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
Related
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.
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 ;)
Really the subject says it all.
<CollectionViewSource x:Key="MyData"
Source="{Binding}" Filter="{ SomethingMagicInXaml? }" />
It's not that I can't have code behind. It just nags at me.
You can do pretty much anything in XAML if you "try hard enough", up to writing whole programs in it.
You will never get around code behind (well, if you use libraries you don't have to write any but the application still relies on it of course), here's an example of what you can do in this specific case:
<CollectionViewSource x:Key="Filtered" Source="{Binding DpData}"
xmlns:me="clr-namespace:Test.MarkupExtensions">
<CollectionViewSource.Filter>
<me:Filter>
<me:PropertyFilter PropertyName="Name" Value="Skeet" />
</me:Filter>
</CollectionViewSource.Filter>
</CollectionViewSource>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.Windows.Data;
using System.Collections.ObjectModel;
using System.Windows;
using System.Text.RegularExpressions;
namespace Test.MarkupExtensions
{
[ContentProperty("Filters")]
class FilterExtension : MarkupExtension
{
private readonly Collection<IFilter> _filters = new Collection<IFilter>();
public ICollection<IFilter> Filters { get { return _filters; } }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new FilterEventHandler((s, e) =>
{
foreach (var filter in Filters)
{
var res = filter.Filter(e.Item);
if (!res)
{
e.Accepted = false;
return;
}
}
e.Accepted = true;
});
}
}
public interface IFilter
{
bool Filter(object item);
}
// Sketchy Example Filter
public class PropertyFilter : DependencyObject, IFilter
{
public static readonly DependencyProperty PropertyNameProperty =
DependencyProperty.Register("PropertyName", typeof(string), typeof(PropertyFilter), new UIPropertyMetadata(null));
public string PropertyName
{
get { return (string)GetValue(PropertyNameProperty); }
set { SetValue(PropertyNameProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(PropertyFilter), new UIPropertyMetadata(null));
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty RegexPatternProperty =
DependencyProperty.Register("RegexPattern", typeof(string), typeof(PropertyFilter), new UIPropertyMetadata(null));
public string RegexPattern
{
get { return (string)GetValue(RegexPatternProperty); }
set { SetValue(RegexPatternProperty, value); }
}
public bool Filter(object item)
{
var type = item.GetType();
var itemValue = type.GetProperty(PropertyName).GetValue(item, null);
if (RegexPattern == null)
{
return (object.Equals(itemValue, Value));
}
else
{
if (itemValue is string == false)
{
throw new Exception("Cannot match non-string with regex.");
}
else
{
return Regex.Match((string)itemValue, RegexPattern).Success;
}
}
}
}
}
Markup extensions are your friend if you want to do something in XAML.
(You might want to spell out the name of the extension, i.e. me:FilterExtension as the on-the-fly checking in Visual Studio may complain without reason, it still compiles and runs of course but the warnings might be annoying.
Also do not expect the CollectionViewSource.Filter to show up in the IntelliSense, it does not expect you to set that handler via XML-element-notation)
Actually you don't even need access to the CollectionViewSource instance, you can filter the source collection directly in the ViewModel:
ICollectionView view = CollectionViewSource.GetDefaultView(collection);
view.Filter = predicate;
(note that ICollectionView.Filter is not an event like CollectionViewSource.Filter, it's a property of type Predicate<object>)
WPF automatically creates a CollectionView—or one of its derived types such as ListCollectionView, or BindingListCollectionView—whenever you bind any IEnumerable-derived source data to an ItemsControl.ItemsSource property. Which type of CollectionView you get depends on the capabilities detected at runtime on the data source you provide.
Sometimes even if you try to explicitly bind your own specific CollectionView-derived type to an ItemsSource, the WPF data binding engine may wrap it (using the internal type CollectionViewProxy).
The automatically-supplied CollectionView instance is created and maintained by the system on a per collection basis (note: not per- UI control or per- bound target). In other words, there will be exactly one globally-shared "Default" view for each s̲o̲u̲r̲c̲e̲ collection that you bind to, and this unique CollectionView instance can be retrieved (or created on demand) at any time by passing the same "original" IEnumerable instance back to the static method CollectionViewSource.GetDefaultView() again.
CollectionView is a shim that is able to keep track of the sorting and/or filtering state without actually altering the source. Therefore, if the same source data is referenced by several different Binding usages each with a different CollectionView, they won't interfere with each other. The "Default" view is intended to optimize the very common--and much simpler--situations where filtering and sorting are not required or expected.
In short, every ItemsControl with a data-bound ItemsSource property will always end up with sorting and filtering capabilities, courtesy of some prevailing CollectionView. You can easily perform filtering/sorting for any given IEnumerable by grabbing and manipulating the "Default" CollectionView from the ItemsControl.Items property, but note that all the data-bound targets in the UI that end up using that view--either because you explicitly bound to CollectionViewSource.GetDefaultView(), or because your source wasn't a CollectionView at all--will all share those same sorting/filtering effects.
What's not often mentioned on this subject is, in addition to binding the source collection to the ItemsSource property of an ItemsControl (as a binding target), you can also "simultaneously" access the effective collection of applied filter/sort results--exposed as a CollectionView-derived instance of System.Windows.Controls.ItemCollection--by binding from the Control's Items property (as a binding source).
This enables numerous simplified XAML scenarios:
If having a single, globally-shared filter/sort capability for the given IEnumerable source is sufficient for your app, then just bind directly to ItemsSource. Still in XAML only, you can then filter/sort the items by treating the Items property on the same Control as an ItemCollection binding source. It has many useful bindable properties for controlling the filter/sort. As noted, filtering/sorting will be shared amongst all UI elements which are bound to the same source IEnumerable in this way. --or--
Create and apply one or more distinct (non-"Default") CollectionView instances yourself. This allows each data-bound target to have independent filter/sort settings. This can also be done in XAML, and/or you can create your own (List)CollectionView-derived classes. This type of approach is well-covered elsewhere, but what I wanted to point out here is that in many cases the XAML can be simplified by using the same technique of data-binding to the ItemsControl.Items property (as a binding source) in order to access the effective CollectionView.
Summary:With XAML alone, you can data-bind to a collection representing the effective results of any current CollectionView filtering/sorting on a WPF ItemsControl by treating its Items property as a read-only binding source. This will be a System.Windows.Controls.ItemCollection which exposes bindable/mutable properties for controlling the active filter and sort criteria.
[edit] - further thoughts:Note that in the simple case of binding your IEnumerable directly to ItemsSource, the ItemCollection you can bind to at ItemsControl.Items will be a wrapper on the original collection's CollectionViewSource.GetDefaultView(). As discussed above, in the case of XAML usage it's a no-brainer to bind to this UI wrapper (via ItemsControl.Items), as opposed to binding to the underlying view it wraps (via CollectionViewSource.GetDefaultView), since the former approach saves you the (in XAML, awkward) trouble of having to explicitly mention any CollectionView at all.
But further, because that ItemCollection wraps the default CollectionView, it seems to me that, even in code-behind (where the choice is less obvious) it's perhaps also more utilitarian to bind to the view promulgated by the UI, since such is best attuned to the de-facto runtime capabilities of both the data source and its UI control target.
I had the following issues with the accepted solution provided by H.B. using .NET FrameWork 4.6.1 (old, I know, but unfortunately a limitation for my current situation):
Severity
Code
Description
Error
XDG0012
The member "Filter" is not recognized or is not accessible.
Error
Cannot set content property 'Filters' on element 'FilterExtension'. 'Filters' has incorrect access level or its assembly does not allow access. Line xx Position yy.
This was easily resolved by changing
public ICollection<IFilter> Filters { get { return _filters; } }
to
public Collection<IFilter> Filters { get { return _filters; } }