Open vampiro4l opened 5 years ago
If you are manually updating already, try raising the PropertyChanged event on the ListCollectionView property instead of calling Refresh(). Wrong
The way these collections work is they create a new immutable collection every time there is a change in the collection, and hand that off to the CollectionView property which is used by WPF. The idea is that you can never change the values in the immutable collection while WPF is iterating over them, otherwise you get inconsistencies in what you see vs what is actually in the collection.
So CollectionView is a new value every time the collection changes. You are getting a snapshot of the collection when you do this:
CollectionViewSource.GetDefaultView(MyCollection.CollectionView)
I haven't tried sorting and filtering in the GUI, I normally do this in the ViewModel. I'll have to create a new example and investigate how this can be done.
For my case, I need a view of the collection that can be filtered and sorted, but not necessarily those operations done on the collection itself, so the .NET 4.5 live collection shaping is great. I've been working an application with needs for high performance, context synchronized collections for years, so this has been an ongoing challenge where i've written and re-written my own collections. You have a really great approach here, thanks for sharing. I'm going to continue looking into this and will let you know if I can come up with anything.
I am having the same issue. I really need to be able to have great speed in loading the collection but then also be able to filter using a Filter for a CollectionViewSource and can't seem to get this to work.
@heavywoody I'll write a manual test for this on Monday. Would this simulate your workload ok?
That would be excellent!
Here is my scenario:
Thank of the stock market. I initially load 500 records
Bound to a WPF Datagrid
I get bursts of 1000-6000 records every few seconds to either add or edit an existing record as the prices constantly change.
The user can edit the values and have complained how I had it setup, when they are editing a value and new values come in, it messes up the editing or freezes UI briefly.
The user has buttons that represent filters so if they click different buttons, different filters are applied runtime.
I am sure I probably just don’t know how to use it right. But thanks for looking into it.
I didn't find much time today to craft an example. So instead I've modified an existing one to add an automatic update feature.
In the ExamplesAndTests directory in this repo I have an EditableDataGridTest application. This is loaded in the main solution that's in the root directory. When running and editing a single row it looks like the screenshot below.
There are 2 checkboxes on the bottom right:
You could have an alias for the EditableCollectionView property that doesn't get updated during an edit. I have an alias in the above example called EditableTestCollectionView
that you can text-search for in the code. The DataGrid has events for when edits begin and end, you could route these events to commands in your view model. I'll have a look at implementing this bit in the sample tomorrow.
Edit: I've given the above approach a go, and it isn't quite working, the edit done in the DataGrid isn't sticking.
New version released, Version 3.3.5, which should appear here https://www.nuget.org/packages/Swordfish.NET.CollectionsV3/3.3.5 when the automatic build script on github has finished compiling and publishing. This adds 2 new methods to ConcurrentObservableCollection:
You would call BeginEditingItem from the WPF DataGrid.BeginningEdit event and call EndedEditingItem from the WPF DataGrid.CurrentCellChanged event
Note that you don't call EndedEditingItem from the WPF DataGrid,CellEditEnding event because that event gets fired before the edit has been commited, and the edit won't persist (as noted in my previous comment).
The EditableDataGridTest example has been updated to use these new helper methods. The xaml inside that demo looks like this:
<DataGrid ItemsSource="{Binding EditableTestCollectionView}" AutoGenerateColumns="True" Grid.Row="1" Grid.Column="1" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="BeginningEdit">
<i:InvokeCommandAction Command="{Binding BeginningEditCommand}"/>
</i:EventTrigger>
<!-- Use CurrentCellChanged event, as the ending-edit events occur before the edit is commited -->
<i:EventTrigger EventName="CurrentCellChanged">
<i:InvokeCommandAction Command="{Binding CellChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
Where i is the namespace xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
from the Microsoft.Xaml.Behaviours.Wpf nuget package found here https://www.nuget.org/packages/Microsoft.Xaml.Behaviors.Wpf
In my view model I handle the commands like this:
private RelayCommandFactory _beginningEditCommand = new RelayCommandFactory();
public ICommand BeginningEditCommand => _beginningEditCommand.GetCommand(() => TestCollection.BeginEditingItem());
private RelayCommandFactory _cellChangedCommand = new RelayCommandFactory();
public ICommand CellChangedCommand => _cellChangedCommand.GetCommand(() => TestCollection.EndedEditingItem();
I'll have to bump investigating the performance issues with sorting and filtering to another day.
I've added a new example called DGGroupSortFilterExample. The Grouping is working, the Filtering is working, the Sorting doesn't work, has to be sorted at the view model end, which in this sample is a matter of changing the following line in the view model from this:
public IList<ProjectDetails> EditableProjectList => TestCollection.EditableCollectionView;
to this:
public IList<ProjectDetails> EditableProjectList =>
TestCollection.EditableCollectionView.OrderBy(o=>o.ProjectName).ThenBy(o=>o.DueDate).ToList();
The interesting thing was the performance problems I was having in the DataGrid with grouping turned on. Apparently Virtualizing gets turned off in the VirtualizingPanel when grouping is enabled. It's re-enabled by turning on VirtualizingPanel.IsVirtualizingWhenGrouping="True"
so my DataGrid element in the xaml looks like this:
<DataGrid x:Name="dataGrid1"
ItemsSource="{Binding Source={StaticResource cvsTasks}}"
CanUserAddRows="False"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True">
I am having the similar issue. in my case, use Listview Controls (WPF), I need change comparer and filter at runtime. and i will use multi-thread to update items data.
i thing.. maybe ConcurrentObservableSortedCollection can provide (change comparer and filter) itself? then i just easy binding CollectionView..
@Kannagi0303 If you want to do filtering and sorting in your view model that's easy (compared to using CollectionViewSource), just have a property in your view model that utilizes the CollectionView, and fires a property changed event for your filtered/sorted view when the CollectionView property changes on ConcurrentObservableSortedCollection.
Here's a code sample of what I'm trying to say. In your ListView xaml code you bind to the FilteredSortedView property below for your ItemsSource
public class ExampleViewModel : INotifyPropertyChanged
{
public ExampleViewModel()
{
// Items is the ConcurrentObservableSortedCollection, listen
// for when the CollectionView changes so we can trigger an
// update using FilteredSortedView
Items.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(Items.CollectionView))
RaiseViewChanged();
};
}
/// <summary>
/// This notifies the ListView that the FilteredSortedView neads to be re-read
/// </summary>
protected void RaiseViewChanged() =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FilteredSortedView)));
/// <summary>
/// Sorts the CollectionView
/// </summary>
private IComparer<string> _sorter;
public IComparer<string> Sorter
{
get => _sorter;
set
{
_sorter = value;
RaiseViewChanged();
}
}
/// <summary>
/// Filters the CollectionView
/// </summary>
private Predicate<string> _filter;
public Predicate<string> Filter
{
get => _filter;
set
{
_filter = value;
RaiseViewChanged();
}
}
/// <summary>
/// The raw items collection
/// </summary>
public ConcurrentObservableSortedCollection<string> Items =
new ConcurrentObservableSortedCollection<string>();
/// <summary>
/// The filtered and sorted collection, that can be directly bound to the ListView
/// ListView.ItemsSource = "{Binding FilteredSortedView}"
/// </summary>
public IEnumerable<string> FilteredSortedView =>
Items
.CollectionView
.Where(i => Filter(i))
.OrderBy(i => i, Sorter);
public event PropertyChangedEventHandler PropertyChanged;
}
Great Wonderful code!~ It's nice work. sample is simple and easy to use Thanks~
Anybody have any ideas on how to get this collection type to play nicely with the ListCollectionView? Currently have a WPF ListView Binding to the CollectionView property of the collection, and creating a ListCollectionView to add live sorting and live filtering on via CollectionViewSource.GetDefaultView(MyCollection.CollectionView)
I would guess that since we are not actually binding to a collection, but rather to a property instead that the live filtering and sorting are not being applied. I've tried catching the property changed event and manually calling ListCollectionView.Refresh() to no avail.