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

Fixes #513 - AdvancedCollectionView not working with NativeAoT #535

Merged
merged 7 commits into from
Oct 30, 2024
61 changes: 7 additions & 54 deletions components/Collections/samples/AdvancedCollectionView.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
title: AdvancedCollectionView
author: nmetulev
description: The AdvancedCollectionView is a collection view implementation that support filtering, sorting and incremental loading. It's meant to be used in a viewmodel.
keywords: AdvancedCollectionView, data, sorting, filtering, Collections
description: The AdvancedCollectionView is a collection view implementation that support filtering, sorting and incremental loading. It's meant to be used in a view or viewmodel.
keywords: AdvancedCollectionView, CollectionViewSource, data, sorting, filtering, Collections
dev_langs:
- csharp
category: Helpers
Expand All @@ -12,70 +12,23 @@ issue-id: 0
icon: Assets/AdvancedCollectionView.png
---

> [!Sample AdvancedCollectionViewSample]

## Usage

In your viewmodel instead of having a public [IEnumerable](https://learn.microsoft.com/dotnet/core/api/system.collections.generic.ienumerable-1) of some sort to be bound to an eg. [Listview](https://learn.microsoft.com/uwp/api/Windows.UI.Xaml.Controls.ListView), create a public AdvancedCollectionView and pass your list in the constructor to it. If you've done that you can use the many useful features it provides:
In your view or viewmodel instead of having a public [IEnumerable](https://learn.microsoft.com/dotnet/core/api/system.collections.generic.ienumerable-1) of some sort to be bound to an eg. [Listview](https://learn.microsoft.com/uwp/api/Windows.UI.Xaml.Controls.ListView), create a public AdvancedCollectionView and pass your list in the constructor to it. If you've done that you can use the many useful features it provides:

* sorting your list using the `SortDirection` helper: specify any number of property names to sort on with the direction desired
* filtering your list using a [Predicate](https://learn.microsoft.com/dotnet/core/api/system.predicate-1): this will automatically filter your list only to the items that pass the check by the predicate provided
* deferring notifications using the `NotificationDeferrer` helper: with a convenient _using_ pattern you can increase performance while doing large-scale modifications in your list by waiting with updates until you've completed your work
* incremental loading: if your source collection supports the feature then AdvancedCollectionView will do as well (it simply forwards the calls)
* live shaping: when constructing the `AdvancedCollectionView` you may specify that the collection use live shaping. This means that the collection will re-filter or re-sort if there are changes to the sort properties or filter properties that are specified using `ObserveFilterProperty`

## Example

```csharp
using CommunityToolkit.WinUI.Collections;

// Grab a sample type
public class Person
{
public string Name { get; set; }
}
The `AdvancedCollectionView` is a good replacement for WPF's `CollectionViewSource`.

// Set up the original list with a few sample items
var oc = new ObservableCollection<Person>
{
new Person { Name = "Staff" },
new Person { Name = "42" },
new Person { Name = "Swan" },
new Person { Name = "Orchid" },
new Person { Name = "15" },
new Person { Name = "Flame" },
new Person { Name = "16" },
new Person { Name = "Arrow" },
new Person { Name = "Tempest" },
new Person { Name = "23" },
new Person { Name = "Pearl" },
new Person { Name = "Hydra" },
new Person { Name = "Lamp Post" },
new Person { Name = "4" },
new Person { Name = "Looking Glass" },
new Person { Name = "8" },
};

// Set up the AdvancedCollectionView with live shaping enabled to filter and sort the original list
var acv = new AdvancedCollectionView(oc, true);

// Let's filter out the integers
int nul;
acv.Filter = x => !int.TryParse(((Person)x).Name, out nul);

// And sort ascending by the property "Name"
acv.SortDescriptions.Add(new SortDescription("Name", SortDirection.Ascending));

// Let's add a Person to the observable collection
var person = new Person { Name = "Aardvark" };
oc.Add(person);
## Example

// Our added person is now at the top of the list, but if we rename this person, we can trigger a re-sort
person.Name = "Zaphod"; // Now a re-sort is triggered and person will be last in the list
The following is a complete example of how to perform ...

// AdvancedCollectionView can be bound to anything that uses collections.
YourListView.ItemsSource = acv;
```
> [!Sample AdvancedCollectionViewSample]

## Remarks

Expand Down
15 changes: 9 additions & 6 deletions components/Collections/samples/AdvancedCollectionViewSample.xaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
<Page x:Class="CollectionsExperiment.Samples.AdvancedCollectionViewSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:AdvancedCollectionViewExperiment.Samples"
xmlns:local="using:CollectionsExperiment.Samples"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

Expand All @@ -17,8 +17,9 @@
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
</Style.Setters>
</Style>
<DataTemplate x:Key="PersonDataTemplate">
<TextBlock Text="{Binding Name}" />
<DataTemplate x:Key="EmployeeDataTemplate"
x:DataType="local:Employee">
<TextBlock Text="{x:Bind Name}" />
</DataTemplate>
</Page.Resources>
<Grid ColumnSpacing="12"
Expand Down Expand Up @@ -51,7 +52,8 @@
<Grid Grid.Row="2"
Style="{StaticResource CardStyle}">
<ListView x:Name="LeftList"
ItemTemplate="{StaticResource PersonDataTemplate}" />
ItemTemplate="{StaticResource EmployeeDataTemplate}"
ItemsSource="{x:Bind EmployeeCollection}" />
</Grid>
<TextBlock Grid.Row="1"
Grid.Column="1"
Expand All @@ -60,7 +62,8 @@
Grid.Column="1"
Style="{StaticResource CardStyle}">
<ListView x:Name="RightList"
ItemTemplate="{StaticResource PersonDataTemplate}" />
ItemTemplate="{StaticResource EmployeeDataTemplate}"
ItemsSource="{x:Bind CollectionView}" />
</Grid>
</Grid>
</Page>
72 changes: 37 additions & 35 deletions components/Collections/samples/AdvancedCollectionViewSample.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,73 @@
// See the LICENSE file in the project root for more information.

using CommunityToolkit.WinUI.Collections;
using System.Diagnostics.CodeAnalysis;

namespace CollectionsExperiment.Samples;

[ToolkitSample(id: nameof(AdvancedCollectionViewSample), "AdvancedCollectionView", description: $"A sample for showing how to create and use a {nameof(AdvancedCollectionView)}.")]
[ToolkitSample(id: nameof(AdvancedCollectionViewSample), "AdvancedCollectionView", description: $"A sample for showing how to create and use a {nameof(AdvancedCollectionView)} for sorting and filtering.")]
public sealed partial class AdvancedCollectionViewSample : Page
{
public ObservableCollection<Person>? oc;
public ObservableCollection<Employee> EmployeeCollection { get; private set; }

public AdvancedCollectionView CollectionView { get; private set; }

public AdvancedCollectionViewSample()
{
this.InitializeComponent();
Setup();
}

[MemberNotNull(nameof(EmployeeCollection))]
[MemberNotNull(nameof(CollectionView))]
private void Setup()
{
// left list
oc = new ObservableCollection<Person>
EmployeeCollection = new()
{
new Person { Name = "Staff" },
new Person { Name = "42" },
new Person { Name = "Swan" },
new Person { Name = "Orchid" },
new Person { Name = "15" },
new Person { Name = "Flame" },
new Person { Name = "16" },
new Person { Name = "Arrow" },
new Person { Name = "Tempest" },
new Person { Name = "23" },
new Person { Name = "Pearl" },
new Person { Name = "Hydra" },
new Person { Name = "Lamp Post" },
new Person { Name = "4" },
new Person { Name = "Looking Glass" },
new Person { Name = "8" },
new() { Name = "Staff" },
new() { Name = "42" },
new() { Name = "Swan" },
new() { Name = "Orchid" },
new() { Name = "15" },
new() { Name = "Flame" },
new() { Name = "16" },
new() { Name = "Arrow" },
new() { Name = "Tempest" },
new() { Name = "23" },
new() { Name = "Pearl" },
new() { Name = "Hydra" },
new() { Name = "Lamp Post" },
new() { Name = "4" },
new() { Name = "Looking Glass" },
new() { Name = "8" },
};

LeftList.ItemsSource = oc;

// right list
var acv = new AdvancedCollectionView(oc);
int nul;
acv.Filter = x => !int.TryParse(((Person)x).Name, out nul);
acv.SortDescriptions.Add(new SortDescription("Name", SortDirection.Ascending));
AdvancedCollectionView acv = new(EmployeeCollection);
acv.Filter = x => !int.TryParse(((Employee)x).Name, out _);
acv.SortDescriptions.Add(new(nameof(Employee.Name), SortDirection.Ascending));

RightList.ItemsSource = acv;
CollectionView = acv;
}

private void Add_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(NewItemBox.Text))
{
oc!.Insert(0, new Person { Name = NewItemBox.Text });
EmployeeCollection.Insert(0, new Employee { Name = NewItemBox.Text });
NewItemBox.Text = "";
}
}
}

/// <summary>
/// A sample class used to show how to use the <see cref="AdvancedCollectionView"/> class.
/// </summary>
public partial class Employee
{
/// <summary>
/// A sample class used to show how to use the <see cref="IIncrementalSource{TSource}"/> interface.
/// Gets or sets the name of the person.
/// </summary>
public class Person
{
/// <summary>
/// Gets or sets the name of the person.
/// </summary>
public string? Name { get; set; }
}
public string? Name { get; set; }
}
6 changes: 6 additions & 0 deletions components/Collections/samples/Collections.Samples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,10 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="PolySharp" Version="1.14.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@
<TextBlock Text="Items are loaded incrementally when the view needs to show them (i.e., when the user scrolls the ListView)"
TextWrapping="Wrap" />
<Button Margin="0,12,0,12"
Click="RefreshCollection"
Click="{x:Bind PeopleSource.RefreshAsync}"
Content="Refresh collection"
Style="{StaticResource AccentButtonStyle}" />
<TextBlock>
<Run Text="Is loading:" />
<Run FontWeight="SemiBold"
Text="{Binding IsLoading, Mode=OneWay}" />
Text="{x:Bind PeopleSource.IsLoading, Mode=OneWay}" />
</TextBlock>
<TextBlock>
<Run Text="Has more items:" />
<Run FontWeight="SemiBold"
Text="{Binding HasMoreItems, Mode=OneWay}" />
Text="{x:Bind PeopleSource.HasMoreItems, Mode=OneWay}" />
</TextBlock>

</StackPanel>
Expand All @@ -43,7 +43,8 @@
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="4">
<ListView x:Name="PeopleListView">
<ListView x:Name="PeopleListView"
ItemsSource="{x:Bind PeopleSource, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Person">
<Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,20 @@ namespace CollectionsExperiment.Samples;
[ToolkitSample(id: nameof(IncrementalLoadingCollectionSample), "Incremental Loading Collection", description: $"A sample for showing how to create and use a IncrementalLoadingCollection.")]
public sealed partial class IncrementalLoadingCollectionSample : Page
{
// IncrementalLoadingCollection can be bound to a GridView or a ListView. In this case it is a ListView called PeopleListView.
public IncrementalLoadingCollection<PeopleSource, Person> PeopleSource { get; set; } = new(new PeopleSource());

public IncrementalLoadingCollectionSample()
{
this.InitializeComponent();
Load();
}
private void Load()
{
// IncrementalLoadingCollection can be bound to a GridView or a ListView. In this case it is a ListView called PeopleListView.
var collection = new IncrementalLoadingCollection<PeopleSource, Person>(new PeopleSource());
PeopleListView.ItemsSource = collection;

// Binds the collection to the page DataContext in order to use its IsLoading and HasMoreItems properties.
DataContext = collection;
}

private async void RefreshCollection(object sender, RoutedEventArgs e)
{
var collection = (IncrementalLoadingCollection<PeopleSource, Person>)PeopleListView.ItemsSource;
await collection.RefreshAsync();
}
}

/// <summary>
/// A sample implementation of the <see cref="IIncrementalSource{TSource}"/> interface.
/// </summary>
/// <seealso cref="IIncrementalSource{TSource}"/>
public class PeopleSource : IIncrementalSource<Person>
public partial class PeopleSource : IIncrementalSource<Person>
{
private readonly List<Person> _people;

Expand Down Expand Up @@ -94,7 +81,7 @@ public PeopleSource()
/// <summary>
/// A sample class used to show how to use the <see cref="IIncrementalSource{TSource}"/> interface.
/// </summary>
public class Person
public partial class Person
{
/// <summary>
/// Gets or sets the name of the person.
Expand Down
2 changes: 1 addition & 1 deletion components/Triggers/src/CompareStateTrigger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace CommunityToolkit.WinUI;
/// <para>
/// Example: Trigger if a value is greater than 0
/// <code lang="xaml">
/// &lt;triggers:CompareStateTrigger Value="{Binding MyValue}" CompareTo="0" Comparison="GreaterThan" />
/// &lt;triggers:CompareStateTrigger Value="{x:Bind MyValue}" CompareTo="0" Comparison="GreaterThan" />
michael-hawker marked this conversation as resolved.
Show resolved Hide resolved
michael-hawker marked this conversation as resolved.
Show resolved Hide resolved
/// </code>
/// </para>
/// </remarks>
Expand Down
2 changes: 1 addition & 1 deletion components/Triggers/src/IsEqualStateTrigger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace CommunityToolkit.WinUI;
/// <para>
/// Example: Trigger if a value is null
/// <code lang="xaml">
/// &lt;triggers:EqualsStateTrigger Value="{Binding MyObject}" EqualTo="{x:Null}" />
/// &lt;triggers:EqualsStateTrigger Value="{x:Bind MyObject}" EqualTo="{x:Null}" />
michael-hawker marked this conversation as resolved.
Show resolved Hide resolved
/// </code>
/// </para>
/// </remarks>
Expand Down
Loading