stewienj / SwordfishCollections

C# library for Concurrent Observable Collection, Dictionary, and Sorted Dictionary
Creative Commons Zero v1.0 Universal
76 stars 11 forks source link

V3 ConcurrentObservableCollection with WPF ListCollectionView #8

Open vampiro4l opened 5 years ago

vampiro4l commented 5 years ago

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.

fjch1997 commented 5 years ago

If you are manually updating already, try raising the PropertyChanged event on the ListCollectionView property instead of calling Refresh(). Wrong

stewienj commented 5 years ago

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.

vampiro4l commented 5 years ago

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.

heavywoody commented 3 years ago

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.

stewienj commented 3 years ago

@heavywoody I'll write a manual test for this on Monday. Would this simulate your workload ok?

heavywoody commented 3 years ago

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.

stewienj commented 3 years ago

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.

EditableDataGridTestScreenshot

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.

stewienj commented 3 years ago

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.

stewienj commented 3 years ago

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">
kannagi0303 commented 3 years ago

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..

stewienj commented 3 years ago

@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;
}
kannagi0303 commented 3 years ago

Great Wonderful code!~ It's nice work. sample is simple and easy to use Thanks~