Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V3 ConcurrentObservableCollection with WPF ListCollectionView #8

Open
vampiro4l opened this issue Oct 29, 2019 · 12 comments
Open

V3 ConcurrentObservableCollection with WPF ListCollectionView #8

vampiro4l opened this issue Oct 29, 2019 · 12 comments

Comments

@vampiro4l
Copy link

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
Copy link
Contributor

fjch1997 commented Oct 29, 2019

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

@stewienj
Copy link
Owner

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
Copy link
Author

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
Copy link

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
Copy link
Owner

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

  • start with a ConcurrentObservableCollection with 0 items, viewed in a WPF DataGrid with filtering, autogenerated columns
  • add 100,000 items
  • then add a burst of 10 items every couple of seconds

@heavywoody
Copy link

heavywoody commented Jan 30, 2021 via email

@stewienj
Copy link
Owner

stewienj commented Feb 1, 2021

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:

  • The left checkbox sets up a timer that adds a new item every second. If you are editing a row in the datagrid when a new item comes in the edit will be cancelled.
  • The right checkbox sets up a timer that randomly updates a random item every tenth of a second. If you are editing a row in the datagrid when the properties of an item are modified, then you can keep on editing.

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 added a commit that referenced this issue Feb 2, 2021
@stewienj
Copy link
Owner

stewienj commented Feb 2, 2021

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:

  • BeginEditingItem
  • EndedEditingItem

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 added a commit that referenced this issue Feb 3, 2021
stewienj added a commit that referenced this issue Feb 3, 2021
@stewienj
Copy link
Owner

stewienj commented Feb 3, 2021

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
Copy link

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
Copy link
Owner

stewienj commented Feb 11, 2021

@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
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants