From 7a3ba94d9f52e47d5c77ee2bdaf6c4ca286180d4 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Mon, 5 Jun 2023 19:18:39 +0200 Subject: [PATCH 1/9] Init --- components/DeveloperTools/OpenSolution.bat | 3 + .../samples/AlignmentGridSample.xaml | 16 ++ .../samples/AlignmentGridSample.xaml.cs | 20 ++ .../DeveloperTools/samples/Dependencies.props | 31 +++ .../samples/DeveloperTools.Samples.csproj | 8 + .../DeveloperTools/samples/DeveloperTools.md | 42 ++++ .../samples/FocusTrackerSample.xaml | 17 ++ .../samples/FocusTrackerSample.xaml.cs | 18 ++ .../src/AdditionalAssemblyInfo.cs | 13 ++ .../DeveloperTools/src/AlignmentGrid.cs | 127 +++++++++++++ ...mmunityToolkit.WinUI.DeveloperTools.csproj | 13 ++ .../DeveloperTools/src/Dependencies.props | 31 +++ .../src/FocusTracker/FocusTracker.cs | 179 ++++++++++++++++++ .../src/FocusTracker/FocusTracker.xaml | 71 +++++++ .../DeveloperTools/src/MultiTarget.props | 9 + .../DeveloperTools/src/Themes/Generic.xaml | 7 + .../tests/DeveloperTools.Tests.projitems | 11 ++ .../tests/DeveloperTools.Tests.shproj | 13 ++ 18 files changed, 629 insertions(+) create mode 100644 components/DeveloperTools/OpenSolution.bat create mode 100644 components/DeveloperTools/samples/AlignmentGridSample.xaml create mode 100644 components/DeveloperTools/samples/AlignmentGridSample.xaml.cs create mode 100644 components/DeveloperTools/samples/Dependencies.props create mode 100644 components/DeveloperTools/samples/DeveloperTools.Samples.csproj create mode 100644 components/DeveloperTools/samples/DeveloperTools.md create mode 100644 components/DeveloperTools/samples/FocusTrackerSample.xaml create mode 100644 components/DeveloperTools/samples/FocusTrackerSample.xaml.cs create mode 100644 components/DeveloperTools/src/AdditionalAssemblyInfo.cs create mode 100644 components/DeveloperTools/src/AlignmentGrid.cs create mode 100644 components/DeveloperTools/src/CommunityToolkit.WinUI.DeveloperTools.csproj create mode 100644 components/DeveloperTools/src/Dependencies.props create mode 100644 components/DeveloperTools/src/FocusTracker/FocusTracker.cs create mode 100644 components/DeveloperTools/src/FocusTracker/FocusTracker.xaml create mode 100644 components/DeveloperTools/src/MultiTarget.props create mode 100644 components/DeveloperTools/src/Themes/Generic.xaml create mode 100644 components/DeveloperTools/tests/DeveloperTools.Tests.projitems create mode 100644 components/DeveloperTools/tests/DeveloperTools.Tests.shproj diff --git a/components/DeveloperTools/OpenSolution.bat b/components/DeveloperTools/OpenSolution.bat new file mode 100644 index 00000000..814a56d4 --- /dev/null +++ b/components/DeveloperTools/OpenSolution.bat @@ -0,0 +1,3 @@ +@ECHO OFF + +powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %* \ No newline at end of file diff --git a/components/DeveloperTools/samples/AlignmentGridSample.xaml b/components/DeveloperTools/samples/AlignmentGridSample.xaml new file mode 100644 index 00000000..175a1e28 --- /dev/null +++ b/components/DeveloperTools/samples/AlignmentGridSample.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/components/DeveloperTools/samples/AlignmentGridSample.xaml.cs b/components/DeveloperTools/samples/AlignmentGridSample.xaml.cs new file mode 100644 index 00000000..7058a9f1 --- /dev/null +++ b/components/DeveloperTools/samples/AlignmentGridSample.xaml.cs @@ -0,0 +1,20 @@ +// 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. + +using CommunityToolkit.WinUI.DeveloperTools; + +namespace DeveloperToolsExperiment.Samples; + +[ToolkitSampleNumericOption("OpacitySetting", 0.2, 0.1, 1.0, 0.1, false, Title = "Opacity")] +[ToolkitSampleNumericOption("HorizontalStep", 20, 5, 100, 1, false, Title = "HorizontalStep")] +[ToolkitSampleNumericOption("VerticalStep", 20, 5, 100, 1, false, Title = "VerticalStep")] + +[ToolkitSample(id: nameof(AlignmentGridSample), "AlignmentGrid", description: $"A sample for showing how to create and use a {nameof(AlignmentGrid)}.")] +public sealed partial class AlignmentGridSample : Page +{ + public AlignmentGridSample() + { + this.InitializeComponent(); + } +} diff --git a/components/DeveloperTools/samples/Dependencies.props b/components/DeveloperTools/samples/Dependencies.props new file mode 100644 index 00000000..e622e1df --- /dev/null +++ b/components/DeveloperTools/samples/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/DeveloperTools/samples/DeveloperTools.Samples.csproj b/components/DeveloperTools/samples/DeveloperTools.Samples.csproj new file mode 100644 index 00000000..7e8d522b --- /dev/null +++ b/components/DeveloperTools/samples/DeveloperTools.Samples.csproj @@ -0,0 +1,8 @@ + + + DeveloperTools + + + + + diff --git a/components/DeveloperTools/samples/DeveloperTools.md b/components/DeveloperTools/samples/DeveloperTools.md new file mode 100644 index 00000000..34297eee --- /dev/null +++ b/components/DeveloperTools/samples/DeveloperTools.md @@ -0,0 +1,42 @@ +--- +title: Developer Tools +author: nmetulev +description: The FocusTracker and AlignmentGrid help you to get more information about and aligning UI elements. +keywords: DeveloperTools, FocusTracker, AlignmentGrid, dev tools, controls +dev_langs: + - csharp +category: Xaml +subcategory: Layout +discussion-id: 0 +issue-id: 0 +--- + +# AlignmentGrid XAML Control + +The [AlignmentGrid Control](/dotnet/api/microsoft.toolkit.uwp.developertools.alignmentgrid) can be used to display a grid to help with aligning controls. + +You can control the grid's steps with `HorizontalStep` and `VerticalStep` properties. Line color can be defined with `LineBrush` property. + +> [!Sample AlignmentGridSample] + +## Properties + +| Property | Type | Description | +| -- | -- | -- | +| HorizontalStep | double | Gets or sets the step to use horizontally | +| LineBrush | Brush | Gets or sets line Brush | +| VerticalStep | double | Gets or sets the step to use vertically | + + +# FocusTracker + +The [FocusTracker Control](/dotnet/api/microsoft.toolkit.uwp.developertools.focustracker) can be used to display information about the current focused XAML element (if any). + +FocusTracker will display the following information (when available) about the current focused XAML element: + +- Name +- Type +- AutomationProperties.Name +- Name of the first parent in hierarchy with a name + +> [!Sample FocusTrackerSample] diff --git a/components/DeveloperTools/samples/FocusTrackerSample.xaml b/components/DeveloperTools/samples/FocusTrackerSample.xaml new file mode 100644 index 00000000..93e4c2bb --- /dev/null +++ b/components/DeveloperTools/samples/FocusTrackerSample.xaml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/components/DeveloperTools/samples/FocusTrackerSample.xaml.cs b/components/DeveloperTools/samples/FocusTrackerSample.xaml.cs new file mode 100644 index 00000000..429887b7 --- /dev/null +++ b/components/DeveloperTools/samples/FocusTrackerSample.xaml.cs @@ -0,0 +1,18 @@ +// 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. + +using CommunityToolkit.WinUI.DeveloperTools; + +namespace DeveloperToolsExperiment.Samples; + +[ToolkitSampleBoolOption("IsActive", true, Title = "IsActive")] + +[ToolkitSample(id: nameof(FocusTrackerSample), "FocusTracker", description: $"A sample for showing how to create and use a {nameof(FocusTracker)}.")] +public sealed partial class FocusTrackerSample : Page +{ + public FocusTrackerSample() + { + this.InitializeComponent(); + } +} diff --git a/components/DeveloperTools/src/AdditionalAssemblyInfo.cs b/components/DeveloperTools/src/AdditionalAssemblyInfo.cs new file mode 100644 index 00000000..683e5390 --- /dev/null +++ b/components/DeveloperTools/src/AdditionalAssemblyInfo.cs @@ -0,0 +1,13 @@ +// 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. + +using System.Runtime.CompilerServices; + +// These `InternalsVisibleTo` calls are intended to make it easier for +// for any internal code to be testable in all the different test projects +// used with the Labs infrastructure. +[assembly: InternalsVisibleTo("DeveloperTools.Tests.Uwp")] +[assembly: InternalsVisibleTo("DeveloperTools.Tests.WinAppSdk")] +[assembly: InternalsVisibleTo("CommunityToolkit.Tests.Uwp")] +[assembly: InternalsVisibleTo("CommunityToolkit.Tests.WinAppSdk")] diff --git a/components/DeveloperTools/src/AlignmentGrid.cs b/components/DeveloperTools/src/AlignmentGrid.cs new file mode 100644 index 00000000..810214cc --- /dev/null +++ b/components/DeveloperTools/src/AlignmentGrid.cs @@ -0,0 +1,127 @@ +// 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. + +#if WINAPPSDK +using Microsoft.UI.Xaml.Shapes; +#else +using Windows.UI.Xaml.Shapes; +#endif + +namespace CommunityToolkit.WinUI.DeveloperTools; +/// +/// AlignmentGrid is used to display a grid to help aligning controls +/// +public partial class AlignmentGrid : ContentControl +{ + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty LineBrushProperty = DependencyProperty.Register(nameof(LineBrush), typeof(Brush), typeof(AlignmentGrid), new PropertyMetadata(null, OnPropertyChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty HorizontalStepProperty = DependencyProperty.Register(nameof(HorizontalStep), typeof(double), typeof(AlignmentGrid), new PropertyMetadata(20.0, OnPropertyChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty VerticalStepProperty = DependencyProperty.Register(nameof(VerticalStep), typeof(double), typeof(AlignmentGrid), new PropertyMetadata(20.0, OnPropertyChanged)); + + private static void OnPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) + { + var alignmentGrid = dependencyObject as AlignmentGrid; + alignmentGrid?.Rebuild(); + } + + private readonly Canvas containerCanvas = new Canvas(); + + /// + /// Gets or sets the step to use horizontally. + /// + public Brush LineBrush + { + get { return (Brush)GetValue(LineBrushProperty); } + set { SetValue(LineBrushProperty, value); } + } + + /// + /// Gets or sets the step to use horizontally. + /// + public double HorizontalStep + { + get { return (double)GetValue(HorizontalStepProperty); } + set { SetValue(HorizontalStepProperty, value); } + } + + /// + /// Gets or sets the step to use horizontally. + /// + public double VerticalStep + { + get { return (double)GetValue(VerticalStepProperty); } + set { SetValue(VerticalStepProperty, value); } + } + + /// + /// Initializes a new instance of the class. + /// + public AlignmentGrid() + { + SizeChanged += AlignmentGrid_SizeChanged; + + IsHitTestVisible = false; + IsTabStop = false; + Opacity = 0.5; + + HorizontalContentAlignment = HorizontalAlignment.Stretch; + VerticalContentAlignment = VerticalAlignment.Stretch; + Content = containerCanvas; + } + + private void Rebuild() + { + containerCanvas.Children.Clear(); + var horizontalStep = HorizontalStep; + var verticalStep = VerticalStep; + var brush = LineBrush ?? (Brush)Application.Current.Resources["AccentFillColorDefaultBrush"]; + + if (horizontalStep > 0) + { + for (double x = 0; x < ActualWidth; x += horizontalStep) + { + var line = new Rectangle + { + Width = 1, + Height = ActualHeight, + Fill = brush + }; + Canvas.SetLeft(line, x); + + containerCanvas.Children.Add(line); + } + } + + if (verticalStep > 0) + { + for (double y = 0; y < ActualHeight; y += verticalStep) + { + var line = new Rectangle + { + Width = ActualWidth, + Height = 1, + Fill = brush + }; + Canvas.SetTop(line, y); + + containerCanvas.Children.Add(line); + } + } + } + + private void AlignmentGrid_SizeChanged(object sender, SizeChangedEventArgs e) + { + Rebuild(); + } +} diff --git a/components/DeveloperTools/src/CommunityToolkit.WinUI.DeveloperTools.csproj b/components/DeveloperTools/src/CommunityToolkit.WinUI.DeveloperTools.csproj new file mode 100644 index 00000000..56dcc128 --- /dev/null +++ b/components/DeveloperTools/src/CommunityToolkit.WinUI.DeveloperTools.csproj @@ -0,0 +1,13 @@ + + + DeveloperTools + This package contains DeveloperTools. + 8.0.0-beta.1 + + + CommunityToolkit.WinUI.DeveloperToolsRns + + + + + diff --git a/components/DeveloperTools/src/Dependencies.props b/components/DeveloperTools/src/Dependencies.props new file mode 100644 index 00000000..e622e1df --- /dev/null +++ b/components/DeveloperTools/src/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/DeveloperTools/src/FocusTracker/FocusTracker.cs b/components/DeveloperTools/src/FocusTracker/FocusTracker.cs new file mode 100644 index 00000000..39feacb2 --- /dev/null +++ b/components/DeveloperTools/src/FocusTracker/FocusTracker.cs @@ -0,0 +1,179 @@ +// 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. + +namespace CommunityToolkit.WinUI.DeveloperTools; + +//// +/// FocusTracker can be used to display information about the current focused XAML element. +/// +[TemplatePart(Name = PartControlName, Type = typeof(TextBlock))] +[TemplatePart(Name = PartControlType, Type = typeof(TextBlock))] +[TemplatePart(Name = PartControlAutomationName, Type = typeof(TextBlock))] +[TemplatePart(Name = PartControlFirstParentWithName, Type = typeof(TextBlock))] +public partial class FocusTracker : Control +{ + internal const string PartControlName = "PART_ControlName"; + internal const string PartControlType = "PART_ControlType"; + internal const string PartControlAutomationName = "PART_ControlAutomationName"; + internal const string PartControlFirstParentWithName = "PART_ControlFirstParentWithName"; + + /// + /// Defines the dependency property. + /// + public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(nameof(IsActive), typeof(bool), typeof(FocusTracker), new PropertyMetadata(false, OnIsActiveChanged)); + + private static void OnIsActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is FocusTracker focusTracker) + { + if (e.NewValue != null && (bool)e.NewValue) + { + focusTracker.Start(); + } + else + { + focusTracker.Stop(); + } + } + } + + private TextBlock? controlName; + private TextBlock? controlType; + private TextBlock? controlAutomationName; + private TextBlock? controlFirstParentWithName; + + /// + /// Gets or sets a value indicating whether the tracker is running or not. + /// + public bool IsActive + { + get { return (bool)GetValue(IsActiveProperty); } + set { SetValue(IsActiveProperty, value); } + } + + /// + /// Initializes a new instance of the class. + /// + public FocusTracker() + { + DefaultStyleKey = typeof(FocusTracker); + } + + /// + /// Update the visual state of the control when its template is changed. + /// + protected override void OnApplyTemplate() + { + controlName = GetTemplateChild(PartControlName) as TextBlock; + controlType = GetTemplateChild(PartControlType) as TextBlock; + controlAutomationName = GetTemplateChild(PartControlAutomationName) as TextBlock; + controlFirstParentWithName = GetTemplateChild(PartControlFirstParentWithName) as TextBlock; + } + + private void Start() + { + // Get currently focused control once when we start + if (Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent("Windows.UI.Xaml.UIElement", "XamlRoot") && XamlRoot != null) + { + if (FocusManager.GetFocusedElement(XamlRoot) is FrameworkElement element) + { + FocusOnControl(element); + } + } + else + { + if (FocusManager.GetFocusedElement() is FrameworkElement element) + { + FocusOnControl(element); + } + } + + // Then use FocusManager event from 1809 to listen to updates + FocusManager.GotFocus += FocusManager_GotFocus!; + } + + private void Stop() + { + FocusManager.GotFocus -= FocusManager_GotFocus!; + ClearContent(); + } + + private void FocusManager_GotFocus(object sender, FocusManagerGotFocusEventArgs e) + { + if (e.NewFocusedElement is FrameworkElement element) + { + FocusOnControl(element); + } + } + + private void ClearContent() + { + if (controlName != null) + { + controlName.Text = string.Empty; + } + + if (controlType != null) + { + controlType.Text = string.Empty; + } + + if (controlAutomationName != null) + { + controlAutomationName.Text = string.Empty; + } + + if (controlFirstParentWithName != null) + { + controlFirstParentWithName.Text = string.Empty; + } + } + + private void FocusOnControl(FrameworkElement focusedControl) + { + if (focusedControl == null) + { + ClearContent(); + return; + } + + if (controlName != null) + { + controlName.Text = focusedControl.Name; + } + + if (controlType != null) + { + controlType.Text = focusedControl.GetType().Name; + } + + if (controlAutomationName != null) + { + controlAutomationName.Text = AutomationProperties.GetName(focusedControl); + } + + if (controlFirstParentWithName != null) + { + var parentWithName = FindVisualAscendantWithName(focusedControl); + controlFirstParentWithName.Text = parentWithName?.Name ?? string.Empty; + } + } + + private FrameworkElement? FindVisualAscendantWithName(FrameworkElement element) + { + var parent = VisualTreeHelper.GetParent(element) as FrameworkElement; + + if (parent == null) + { + return null; + } + + if (!string.IsNullOrEmpty(parent.Name)) + { + return parent; + } + + return FindVisualAscendantWithName(parent); + } +} diff --git a/components/DeveloperTools/src/FocusTracker/FocusTracker.xaml b/components/DeveloperTools/src/FocusTracker/FocusTracker.xaml new file mode 100644 index 00000000..39a100a5 --- /dev/null +++ b/components/DeveloperTools/src/FocusTracker/FocusTracker.xaml @@ -0,0 +1,71 @@ + + + + diff --git a/components/DeveloperTools/src/MultiTarget.props b/components/DeveloperTools/src/MultiTarget.props new file mode 100644 index 00000000..b11c1942 --- /dev/null +++ b/components/DeveloperTools/src/MultiTarget.props @@ -0,0 +1,9 @@ + + + + uwp;wasdk;wpf;wasm;linuxgtk;macos;ios;android; + + \ No newline at end of file diff --git a/components/DeveloperTools/src/Themes/Generic.xaml b/components/DeveloperTools/src/Themes/Generic.xaml new file mode 100644 index 00000000..7b62894d --- /dev/null +++ b/components/DeveloperTools/src/Themes/Generic.xaml @@ -0,0 +1,7 @@ + + + + + + diff --git a/components/DeveloperTools/tests/DeveloperTools.Tests.projitems b/components/DeveloperTools/tests/DeveloperTools.Tests.projitems new file mode 100644 index 00000000..01b6543c --- /dev/null +++ b/components/DeveloperTools/tests/DeveloperTools.Tests.projitems @@ -0,0 +1,11 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 5034CE36-00F1-493C-88F7-93809DA5DDB6 + + + DeveloperToolsExperiment.Tests + + \ No newline at end of file diff --git a/components/DeveloperTools/tests/DeveloperTools.Tests.shproj b/components/DeveloperTools/tests/DeveloperTools.Tests.shproj new file mode 100644 index 00000000..c3991ea6 --- /dev/null +++ b/components/DeveloperTools/tests/DeveloperTools.Tests.shproj @@ -0,0 +1,13 @@ + + + + 5034CE36-00F1-493C-88F7-93809DA5DDB6 + 14.0 + + + + + + + + From 96130b99b074013badf8a48cbd0d40093cb5626c Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Fri, 26 May 2023 23:36:06 +0200 Subject: [PATCH 2/9] Init --- .../OpenSolution.bat | 3 + .../samples/Dependencies.props | 31 ++++ ...ncrementalLoadingCollection.Samples.csproj | 8 ++ .../samples/IncrementalLoadingCollection.md | 32 +++++ ...rementalLoadingCollectionCustomSample.xaml | 25 ++++ ...entalLoadingCollectionCustomSample.xaml.cs | 30 ++++ .../src/AdditionalAssemblyInfo.cs | 13 ++ ...ntrols.IncrementalLoadingCollection.csproj | 13 ++ .../src/Dependencies.props | 31 ++++ .../src/IncrementalLoadingCollection.cs | 108 ++++++++++++++ .../src/MultiTarget.props | 9 ++ ...leIncrementalLoadingCollectionTestClass.cs | 134 ++++++++++++++++++ ...eIncrementalLoadingCollectionTestPage.xaml | 14 ++ ...crementalLoadingCollectionTestPage.xaml.cs | 16 +++ ...crementalLoadingCollection.Tests.projitems | 23 +++ .../IncrementalLoadingCollection.Tests.shproj | 13 ++ 16 files changed, 503 insertions(+) create mode 100644 components/IncrementalLoadingCollection/OpenSolution.bat create mode 100644 components/IncrementalLoadingCollection/samples/Dependencies.props create mode 100644 components/IncrementalLoadingCollection/samples/IncrementalLoadingCollection.Samples.csproj create mode 100644 components/IncrementalLoadingCollection/samples/IncrementalLoadingCollection.md create mode 100644 components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml create mode 100644 components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml.cs create mode 100644 components/IncrementalLoadingCollection/src/AdditionalAssemblyInfo.cs create mode 100644 components/IncrementalLoadingCollection/src/CommunityToolkit.WinUI.Controls.IncrementalLoadingCollection.csproj create mode 100644 components/IncrementalLoadingCollection/src/Dependencies.props create mode 100644 components/IncrementalLoadingCollection/src/IncrementalLoadingCollection.cs create mode 100644 components/IncrementalLoadingCollection/src/MultiTarget.props create mode 100644 components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestClass.cs create mode 100644 components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml create mode 100644 components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml.cs create mode 100644 components/IncrementalLoadingCollection/tests/IncrementalLoadingCollection.Tests.projitems create mode 100644 components/IncrementalLoadingCollection/tests/IncrementalLoadingCollection.Tests.shproj diff --git a/components/IncrementalLoadingCollection/OpenSolution.bat b/components/IncrementalLoadingCollection/OpenSolution.bat new file mode 100644 index 00000000..814a56d4 --- /dev/null +++ b/components/IncrementalLoadingCollection/OpenSolution.bat @@ -0,0 +1,3 @@ +@ECHO OFF + +powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %* \ No newline at end of file diff --git a/components/IncrementalLoadingCollection/samples/Dependencies.props b/components/IncrementalLoadingCollection/samples/Dependencies.props new file mode 100644 index 00000000..e622e1df --- /dev/null +++ b/components/IncrementalLoadingCollection/samples/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollection.Samples.csproj b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollection.Samples.csproj new file mode 100644 index 00000000..4231b814 --- /dev/null +++ b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollection.Samples.csproj @@ -0,0 +1,8 @@ + + + IncrementalLoadingCollection + + + + + diff --git a/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollection.md b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollection.md new file mode 100644 index 00000000..454fe6a9 --- /dev/null +++ b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollection.md @@ -0,0 +1,32 @@ +--- +title: IncrementalLoadingCollection +author: githubaccount +description: TODO: Your experiment's description here +keywords: IncrementalLoadingCollection, Control, Layout +dev_langs: + - csharp +category: Controls +subcategory: Layout +discussion-id: 0 +issue-id: 0 +--- + + + + + + + + + +# IncrementalLoadingCollection + +TODO: Fill in information about this experiment and how to get started here... + +## Custom Control + +You can inherit from an existing component as well, like `Panel`, this example shows a control without a +XAML Style that will be more light-weight to consume by an app developer: + +> [!Sample IncrementalLoadingCollectionCustomSample] + diff --git a/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml new file mode 100644 index 00000000..37bc9647 --- /dev/null +++ b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml.cs b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml.cs new file mode 100644 index 00000000..e4d67a44 --- /dev/null +++ b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml.cs @@ -0,0 +1,30 @@ +// 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. + +using CommunityToolkit.WinUI.Controls; + +namespace IncrementalLoadingCollectionExperiment.Samples; + +/// +/// An example sample page of a custom control inheriting from Panel. +/// +[ToolkitSampleTextOption("TitleText", "This is a title", Title = "Input the text")] +[ToolkitSampleMultiChoiceOption("LayoutOrientation", "Horizontal", "Vertical", Title = "Orientation")] + +[ToolkitSample(id: nameof(IncrementalLoadingCollectionCustomSample), "Custom control", description: $"A sample for showing how to create and use a {nameof(IncrementalLoadingCollection)} custom control.")] +public sealed partial class IncrementalLoadingCollectionCustomSample : Page +{ + public IncrementalLoadingCollectionCustomSample() + { + this.InitializeComponent(); + } + + // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149 + public static Orientation ConvertStringToOrientation(string orientation) => orientation switch + { + "Vertical" => Orientation.Vertical, + "Horizontal" => Orientation.Horizontal, + _ => throw new System.NotImplementedException(), + }; +} diff --git a/components/IncrementalLoadingCollection/src/AdditionalAssemblyInfo.cs b/components/IncrementalLoadingCollection/src/AdditionalAssemblyInfo.cs new file mode 100644 index 00000000..152eadae --- /dev/null +++ b/components/IncrementalLoadingCollection/src/AdditionalAssemblyInfo.cs @@ -0,0 +1,13 @@ +// 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. + +using System.Runtime.CompilerServices; + +// These `InternalsVisibleTo` calls are intended to make it easier for +// for any internal code to be testable in all the different test projects +// used with the Labs infrastructure. +[assembly: InternalsVisibleTo("IncrementalLoadingCollection.Tests.Uwp")] +[assembly: InternalsVisibleTo("IncrementalLoadingCollection.Tests.WinAppSdk")] +[assembly: InternalsVisibleTo("CommunityToolkit.Tests.Uwp")] +[assembly: InternalsVisibleTo("CommunityToolkit.Tests.WinAppSdk")] diff --git a/components/IncrementalLoadingCollection/src/CommunityToolkit.WinUI.Controls.IncrementalLoadingCollection.csproj b/components/IncrementalLoadingCollection/src/CommunityToolkit.WinUI.Controls.IncrementalLoadingCollection.csproj new file mode 100644 index 00000000..98ba449d --- /dev/null +++ b/components/IncrementalLoadingCollection/src/CommunityToolkit.WinUI.Controls.IncrementalLoadingCollection.csproj @@ -0,0 +1,13 @@ + + + IncrementalLoadingCollection + This package contains IncrementalLoadingCollection. + 0.0.1 + + + CommunityToolkit.WinUI.Controls.IncrementalLoadingCollectionRns + + + + + diff --git a/components/IncrementalLoadingCollection/src/Dependencies.props b/components/IncrementalLoadingCollection/src/Dependencies.props new file mode 100644 index 00000000..e622e1df --- /dev/null +++ b/components/IncrementalLoadingCollection/src/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/IncrementalLoadingCollection/src/IncrementalLoadingCollection.cs b/components/IncrementalLoadingCollection/src/IncrementalLoadingCollection.cs new file mode 100644 index 00000000..2a5cca60 --- /dev/null +++ b/components/IncrementalLoadingCollection/src/IncrementalLoadingCollection.cs @@ -0,0 +1,108 @@ +// 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. + +namespace CommunityToolkit.WinUI.Controls; + +/// +/// This is an example control based off of the BoxPanel sample here: https://docs.microsoft.com/windows/apps/design/layout/boxpanel-example-custom-panel. If you need this similar sort of layout component for an application, see UniformGrid in the Toolkit. +/// It is provided as an example of how to inherit from another control like . +/// You can choose to start here or from the or example components. Remove unused components and rename as appropriate. +/// +public partial class IncrementalLoadingCollection : Panel +{ + /// + /// Identifies the property. + /// + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(IncrementalLoadingCollection), new PropertyMetadata(null, OnOrientationChanged)); + + /// + /// Gets the preference of the rows/columns when there are a non-square number of children. Defaults to Vertical. + /// + public Orientation Orientation + { + get { return (Orientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } + + // Invalidate our layout when the property changes. + private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) + { + if (dependencyObject is IncrementalLoadingCollection panel) + { + panel.InvalidateMeasure(); + } + } + + // Store calculations we want to use between the Measure and Arrange methods. + int _columnCount; + double _cellWidth, _cellHeight; + + protected override Size MeasureOverride(Size availableSize) + { + // Determine the square that can contain this number of items. + var maxrc = (int)Math.Ceiling(Math.Sqrt(Children.Count)); + // Get an aspect ratio from availableSize, decides whether to trim row or column. + var aspectratio = availableSize.Width / availableSize.Height; + if (Orientation == Orientation.Vertical) { aspectratio = 1 / aspectratio; } + + int rowcount; + + // Now trim this square down to a rect, many times an entire row or column can be omitted. + if (aspectratio > 1) + { + rowcount = maxrc; + _columnCount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc; + } + else + { + rowcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc; + _columnCount = maxrc; + } + + // Now that we have a column count, divide available horizontal, that's our cell width. + _cellWidth = (int)Math.Floor(availableSize.Width / _columnCount); + // Next get a cell height, same logic of dividing available vertical by rowcount. + _cellHeight = Double.IsInfinity(availableSize.Height) ? Double.PositiveInfinity : availableSize.Height / rowcount; + + double maxcellheight = 0; + + foreach (UIElement child in Children) + { + child.Measure(new Size(_cellWidth, _cellHeight)); + maxcellheight = (child.DesiredSize.Height > maxcellheight) ? child.DesiredSize.Height : maxcellheight; + } + + return LimitUnboundedSize(availableSize, maxcellheight); + } + + // This method limits the panel height when no limit is imposed by the panel's parent. + // That can happen to height if the panel is close to the root of main app window. + // In this case, base the height of a cell on the max height from desired size + // and base the height of the panel on that number times the #rows. + Size LimitUnboundedSize(Size input, double maxcellheight) + { + if (Double.IsInfinity(input.Height)) + { + input.Height = maxcellheight * _columnCount; + _cellHeight = maxcellheight; + } + return input; + } + + protected override Size ArrangeOverride(Size finalSize) + { + int count = 1; + double x, y; + foreach (UIElement child in Children) + { + x = (count - 1) % _columnCount * _cellWidth; + y = ((int)(count - 1) / _columnCount) * _cellHeight; + Point anchorPoint = new Point(x, y); + child.Arrange(new Rect(anchorPoint, child.DesiredSize)); + count++; + } + return finalSize; + } +} diff --git a/components/IncrementalLoadingCollection/src/MultiTarget.props b/components/IncrementalLoadingCollection/src/MultiTarget.props new file mode 100644 index 00000000..b11c1942 --- /dev/null +++ b/components/IncrementalLoadingCollection/src/MultiTarget.props @@ -0,0 +1,9 @@ + + + + uwp;wasdk;wpf;wasm;linuxgtk;macos;ios;android; + + \ No newline at end of file diff --git a/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestClass.cs b/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestClass.cs new file mode 100644 index 00000000..30007f43 --- /dev/null +++ b/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestClass.cs @@ -0,0 +1,134 @@ +// 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. + +using CommunityToolkit.Tooling.TestGen; +using CommunityToolkit.Tests; +using CommunityToolkit.WinUI.Controls; + +namespace IncrementalLoadingCollectionExperiment.Tests; + +[TestClass] +public partial class ExampleIncrementalLoadingCollectionTestClass : VisualUITestBase +{ + // If you don't need access to UI objects directly or async code, use this pattern. + [TestMethod] + public void SimpleSynchronousExampleTest() + { + var assembly = typeof(IncrementalLoadingCollection).Assembly; + var type = assembly.GetType(typeof(IncrementalLoadingCollection).FullName ?? string.Empty); + + Assert.IsNotNull(type, "Could not find IncrementalLoadingCollection type."); + Assert.AreEqual(typeof(IncrementalLoadingCollection), type, "Type of IncrementalLoadingCollection does not match expected type."); + } + + // If you don't need access to UI objects directly, use this pattern. + [TestMethod] + public async Task SimpleAsyncExampleTest() + { + await Task.Delay(250); + + Assert.IsTrue(true); + } + + // Example that shows how to check for exception throwing. + [TestMethod] + public void SimpleExceptionCheckTest() + { + // If you need to check exceptions occur for invalid inputs, etc... + // Use Assert.ThrowsException to limit the scope to where you expect the error to occur. + // Otherwise, using the ExpectedException attribute could swallow or + // catch other issues in setup code. + Assert.ThrowsException(() => throw new NotImplementedException()); + } + + // The UIThreadTestMethod automatically dispatches to the UI for us to work with UI objects. + [UIThreadTestMethod] + public void SimpleUIAttributeExampleTest() + { + var component = new IncrementalLoadingCollection(); + Assert.IsNotNull(component); + } + + // The UIThreadTestMethod can also easily grab a XAML Page for us by passing its type as a parameter. + // This lets us actually test a control as it would behave within an actual application. + // The page will already be loaded by the time your test is called. + [UIThreadTestMethod] + public void SimpleUIExamplePageTest(ExampleIncrementalLoadingCollectionTestPage page) + { + // You can use the Toolkit Visual Tree helpers here to find the component by type or name: + var component = page.FindDescendant(); + + Assert.IsNotNull(component); + + var componentByName = page.FindDescendant("IncrementalLoadingCollectionControl"); + + Assert.IsNotNull(componentByName); + } + + // You can still do async work with a UIThreadTestMethod as well. + [UIThreadTestMethod] + public async Task SimpleAsyncUIExamplePageTest(ExampleIncrementalLoadingCollectionTestPage page) + { + // This helper can be used to wait for a rendering pass to complete. + // Note, this is already done by loading a Page with the [UIThreadTestMethod] helper. + await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { }); + + var component = page.FindDescendant(); + + Assert.IsNotNull(component); + } + + //// ----------------------------- ADVANCED TEST SCENARIOS ----------------------------- + + // If you need to use DataRow, you can use this pattern with the UI dispatch still. + // Otherwise, checkout the UIThreadTestMethod attribute above. + // See https://github.com/CommunityToolkit/Labs-Windows/issues/186 + [TestMethod] + public async Task ComplexAsyncUIExampleTest() + { + await EnqueueAsync(() => + { + var component = new IncrementalLoadingCollection_ClassicBinding(); + Assert.IsNotNull(component); + }); + } + + // If you want to load other content not within a XAML page using the UIThreadTestMethod above. + // Then you can do that using the Load/UnloadTestContentAsync methods. + [TestMethod] + public async Task ComplexAsyncLoadUIExampleTest() + { + await EnqueueAsync(async () => + { + var component = new IncrementalLoadingCollection_ClassicBinding(); + Assert.IsNotNull(component); + Assert.IsFalse(component.IsLoaded); + + await LoadTestContentAsync(component); + + Assert.IsTrue(component.IsLoaded); + + await UnloadTestContentAsync(component); + + Assert.IsFalse(component.IsLoaded); + }); + } + + // You can still use the UIThreadTestMethod to remove the extra layer for the dispatcher as well: + [UIThreadTestMethod] + public async Task ComplexAsyncLoadUIExampleWithoutDispatcherTest() + { + var component = new IncrementalLoadingCollection_ClassicBinding(); + Assert.IsNotNull(component); + Assert.IsFalse(component.IsLoaded); + + await LoadTestContentAsync(component); + + Assert.IsTrue(component.IsLoaded); + + await UnloadTestContentAsync(component); + + Assert.IsFalse(component.IsLoaded); + } +} diff --git a/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml b/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml new file mode 100644 index 00000000..2fae6ef0 --- /dev/null +++ b/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml.cs b/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml.cs new file mode 100644 index 00000000..e72815f8 --- /dev/null +++ b/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml.cs @@ -0,0 +1,16 @@ +// 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. + +namespace IncrementalLoadingCollectionExperiment.Tests; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class ExampleIncrementalLoadingCollectionTestPage : Page +{ + public ExampleIncrementalLoadingCollectionTestPage() + { + this.InitializeComponent(); + } +} diff --git a/components/IncrementalLoadingCollection/tests/IncrementalLoadingCollection.Tests.projitems b/components/IncrementalLoadingCollection/tests/IncrementalLoadingCollection.Tests.projitems new file mode 100644 index 00000000..f4e8bbc1 --- /dev/null +++ b/components/IncrementalLoadingCollection/tests/IncrementalLoadingCollection.Tests.projitems @@ -0,0 +1,23 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + D1181822-7162-459C-B40E-591420DC438F + + + IncrementalLoadingCollectionExperiment.Tests + + + + + ExampleIncrementalLoadingCollectionTestPage.xaml + + + + + Designer + MSBuild:Compile + + + \ No newline at end of file diff --git a/components/IncrementalLoadingCollection/tests/IncrementalLoadingCollection.Tests.shproj b/components/IncrementalLoadingCollection/tests/IncrementalLoadingCollection.Tests.shproj new file mode 100644 index 00000000..a3734868 --- /dev/null +++ b/components/IncrementalLoadingCollection/tests/IncrementalLoadingCollection.Tests.shproj @@ -0,0 +1,13 @@ + + + + D1181822-7162-459C-B40E-591420DC438F + 14.0 + + + + + + + + From 9c1075b4a181d7803b6b8b75916adf7dec949556 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 30 May 2023 10:04:04 +0200 Subject: [PATCH 3/9] Update IncrementalLoadingCollection.cs --- .../src/IncrementalLoadingCollection.cs | 344 ++++++++++++++---- 1 file changed, 267 insertions(+), 77 deletions(-) diff --git a/components/IncrementalLoadingCollection/src/IncrementalLoadingCollection.cs b/components/IncrementalLoadingCollection/src/IncrementalLoadingCollection.cs index 2a5cca60..a1156fb1 100644 --- a/components/IncrementalLoadingCollection/src/IncrementalLoadingCollection.cs +++ b/components/IncrementalLoadingCollection/src/IncrementalLoadingCollection.cs @@ -2,107 +2,297 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace CommunityToolkit.WinUI.Controls; - -/// -/// This is an example control based off of the BoxPanel sample here: https://docs.microsoft.com/windows/apps/design/layout/boxpanel-example-custom-panel. If you need this similar sort of layout component for an application, see UniformGrid in the Toolkit. -/// It is provided as an example of how to inherit from another control like . -/// You can choose to start here or from the or example components. Remove unused components and rename as appropriate. -/// -public partial class IncrementalLoadingCollection : Panel +namespace CommunityToolkit.WinUI { /// - /// Identifies the property. + /// This class represents an whose items can be loaded incrementally. /// - public static readonly DependencyProperty OrientationProperty = - DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(IncrementalLoadingCollection), new PropertyMetadata(null, OnOrientationChanged)); - - /// - /// Gets the preference of the rows/columns when there are a non-square number of children. Defaults to Vertical. - /// - public Orientation Orientation + /// + /// The data source that must be loaded incrementally. + /// + /// + /// The type of collection items. + /// + /// + /// + public class IncrementalLoadingCollection : ObservableCollection, + ISupportIncrementalLoading + where TSource : IIncrementalSource { - get { return (Orientation)GetValue(OrientationProperty); } - set { SetValue(OrientationProperty, value); } - } + private readonly SemaphoreSlim _mutex = new SemaphoreSlim(1); - // Invalidate our layout when the property changes. - private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) - { - if (dependencyObject is IncrementalLoadingCollection panel) + /// + /// Gets or sets an that is called when a retrieval operation begins. + /// + public Action OnStartLoading { get; set; } + + /// + /// Gets or sets an that is called when a retrieval operation ends. + /// + public Action OnEndLoading { get; set; } + + /// + /// Gets or sets an that is called if an error occurs during data retrieval. The actual is passed as an argument. + /// + public Action OnError { get; set; } + + /// + /// Gets a value indicating the source of incremental loading. + /// + protected TSource Source { get; } + + /// + /// Gets a value indicating how many items that must be retrieved for each incremental call. + /// + protected int ItemsPerPage { get; } + + /// + /// Gets or sets a value indicating The zero-based index of the current items page. + /// + protected int CurrentPageIndex { get; set; } + + private bool _isLoading; + private bool _hasMoreItems; + private CancellationToken _cancellationToken; + private bool _refreshOnLoad; + + /// + /// Gets a value indicating whether new items are being loaded. + /// + public bool IsLoading { - panel.InvalidateMeasure(); + get + { + return _isLoading; + } + + private set + { + if (value != _isLoading) + { + _isLoading = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsLoading))); + + if (_isLoading) + { + OnStartLoading?.Invoke(); + } + else + { + OnEndLoading?.Invoke(); + } + } + } } - } - // Store calculations we want to use between the Measure and Arrange methods. - int _columnCount; - double _cellWidth, _cellHeight; + /// + /// Gets a value indicating whether the collection contains more items to retrieve. + /// + public bool HasMoreItems + { + get + { + if (_cancellationToken.IsCancellationRequested) + { + return false; + } - protected override Size MeasureOverride(Size availableSize) - { - // Determine the square that can contain this number of items. - var maxrc = (int)Math.Ceiling(Math.Sqrt(Children.Count)); - // Get an aspect ratio from availableSize, decides whether to trim row or column. - var aspectratio = availableSize.Width / availableSize.Height; - if (Orientation == Orientation.Vertical) { aspectratio = 1 / aspectratio; } + return _hasMoreItems; + } - int rowcount; + private set + { + if (value != _hasMoreItems) + { + _hasMoreItems = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(HasMoreItems))); + } + } + } - // Now trim this square down to a rect, many times an entire row or column can be omitted. - if (aspectratio > 1) + /// + /// Initializes a new instance of the class optionally specifying how many items to load for each data page. + /// + /// + /// The number of items to retrieve for each call. Default is 20. + /// + /// + /// An that is called when a retrieval operation begins. + /// + /// + /// An that is called when a retrieval operation ends. + /// + /// + /// An that is called if an error occurs during data retrieval. + /// + /// + public IncrementalLoadingCollection(int itemsPerPage = 20, Action onStartLoading = null, Action onEndLoading = null, Action onError = null) + : this(Activator.CreateInstance(), itemsPerPage, onStartLoading, onEndLoading, onError) { - rowcount = maxrc; - _columnCount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc; } - else + + /// + /// Initializes a new instance of the class using the specified implementation and, optionally, how many items to load for each data page. + /// + /// + /// An implementation of the interface that contains the logic to actually load data incrementally. + /// + /// + /// The number of items to retrieve for each call. Default is 20. + /// + /// + /// An that is called when a retrieval operation begins. + /// + /// + /// An that is called when a retrieval operation ends. + /// + /// + /// An that is called if an error occurs during data retrieval. + /// + /// + public IncrementalLoadingCollection(TSource source, int itemsPerPage = 20, Action onStartLoading = null, Action onEndLoading = null, Action onError = null) { - rowcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc; - _columnCount = maxrc; - } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } - // Now that we have a column count, divide available horizontal, that's our cell width. - _cellWidth = (int)Math.Floor(availableSize.Width / _columnCount); - // Next get a cell height, same logic of dividing available vertical by rowcount. - _cellHeight = Double.IsInfinity(availableSize.Height) ? Double.PositiveInfinity : availableSize.Height / rowcount; + Source = source; - double maxcellheight = 0; + OnStartLoading = onStartLoading; + OnEndLoading = onEndLoading; + OnError = onError; - foreach (UIElement child in Children) - { - child.Measure(new Size(_cellWidth, _cellHeight)); - maxcellheight = (child.DesiredSize.Height > maxcellheight) ? child.DesiredSize.Height : maxcellheight; + ItemsPerPage = itemsPerPage; + _hasMoreItems = true; } - return LimitUnboundedSize(availableSize, maxcellheight); - } + /// + /// Initializes incremental loading from the view. + /// + /// + /// The number of items to load. + /// + /// + /// An object of the that specifies how many items have been actually retrieved. + /// + public IAsyncOperation LoadMoreItemsAsync(uint count) + => LoadMoreItemsAsync(count, new CancellationToken(false)).AsAsyncOperation(); - // This method limits the panel height when no limit is imposed by the panel's parent. - // That can happen to height if the panel is close to the root of main app window. - // In this case, base the height of a cell on the max height from desired size - // and base the height of the panel on that number times the #rows. - Size LimitUnboundedSize(Size input, double maxcellheight) - { - if (Double.IsInfinity(input.Height)) + /// + /// Clears the collection and triggers/forces a reload of the first page + /// + /// This method does not return a result + public Task RefreshAsync() { - input.Height = maxcellheight * _columnCount; - _cellHeight = maxcellheight; + if (IsLoading) + { + _refreshOnLoad = true; + } + else + { + var previousCount = Count; + Clear(); + CurrentPageIndex = 0; + HasMoreItems = true; + + if (previousCount == 0) + { + // When the list was empty before clearing, the automatic reload isn't fired, so force a reload. + return LoadMoreItemsAsync(0).AsTask(); + } + } + + return Task.CompletedTask; } - return input; - } - protected override Size ArrangeOverride(Size finalSize) - { - int count = 1; - double x, y; - foreach (UIElement child in Children) + /// + /// Actually performs the incremental loading. + /// + /// + /// Used to propagate notification that operation should be canceled. + /// + /// + /// Returns a collection of . + /// + protected virtual async Task> LoadDataAsync(CancellationToken cancellationToken) { - x = (count - 1) % _columnCount * _cellWidth; - y = ((int)(count - 1) / _columnCount) * _cellHeight; - Point anchorPoint = new Point(x, y); - child.Arrange(new Rect(anchorPoint, child.DesiredSize)); - count++; + var result = await Source.GetPagedItemsAsync(CurrentPageIndex, ItemsPerPage, cancellationToken) + .ContinueWith( + t => + { + if (t.IsFaulted) + { + throw t.Exception; + } + + if (t.IsCompletedSuccessfully) + { + CurrentPageIndex += 1; + } + + return t.Result; + }, cancellationToken); + + return result; + } + + private async Task LoadMoreItemsAsync(uint count, CancellationToken cancellationToken) + { + uint resultCount = 0; + _cancellationToken = cancellationToken; + + // TODO (2021.05.05): Make use common AsyncMutex class. + // AsyncMutex is located at CommunityToolkit.WinUI.UI.Media/Extensions/System.Threading.Tasks/AsyncMutex.cs at the time of this note. + await _mutex.WaitAsync(); + try + { + if (!_cancellationToken.IsCancellationRequested) + { + IEnumerable data = null; + try + { + IsLoading = true; + data = await LoadDataAsync(_cancellationToken); + } + catch (OperationCanceledException) + { + // The operation has been canceled using the Cancellation Token. + } + catch (Exception ex) when (OnError != null) + { + OnError.Invoke(ex); + } + + if (data != null && data.Any() && !_cancellationToken.IsCancellationRequested) + { + resultCount = (uint)data.Count(); + + foreach (var item in data) + { + Add(item); + } + } + else + { + HasMoreItems = false; + } + } + } + finally + { + IsLoading = false; + + if (_refreshOnLoad) + { + _refreshOnLoad = false; + await RefreshAsync(); + } + + _mutex.Release(); + } + + return new LoadMoreItemsResult { Count = resultCount }; } - return finalSize; } } From 69131bf6d6b022f120a7dbc5d06aa409ff93c843 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 30 May 2023 19:44:16 +0200 Subject: [PATCH 4/9] Remove files --- ...rementalLoadingCollectionCustomSample.xaml | 25 ---------------- ...entalLoadingCollectionCustomSample.xaml.cs | 30 ------------------- ...ntrols.IncrementalLoadingCollection.csproj | 13 -------- 3 files changed, 68 deletions(-) delete mode 100644 components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml delete mode 100644 components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml.cs delete mode 100644 components/IncrementalLoadingCollection/src/CommunityToolkit.WinUI.Controls.IncrementalLoadingCollection.csproj diff --git a/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml deleted file mode 100644 index 37bc9647..00000000 --- a/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml.cs b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml.cs deleted file mode 100644 index e4d67a44..00000000 --- a/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionCustomSample.xaml.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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. - -using CommunityToolkit.WinUI.Controls; - -namespace IncrementalLoadingCollectionExperiment.Samples; - -/// -/// An example sample page of a custom control inheriting from Panel. -/// -[ToolkitSampleTextOption("TitleText", "This is a title", Title = "Input the text")] -[ToolkitSampleMultiChoiceOption("LayoutOrientation", "Horizontal", "Vertical", Title = "Orientation")] - -[ToolkitSample(id: nameof(IncrementalLoadingCollectionCustomSample), "Custom control", description: $"A sample for showing how to create and use a {nameof(IncrementalLoadingCollection)} custom control.")] -public sealed partial class IncrementalLoadingCollectionCustomSample : Page -{ - public IncrementalLoadingCollectionCustomSample() - { - this.InitializeComponent(); - } - - // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149 - public static Orientation ConvertStringToOrientation(string orientation) => orientation switch - { - "Vertical" => Orientation.Vertical, - "Horizontal" => Orientation.Horizontal, - _ => throw new System.NotImplementedException(), - }; -} diff --git a/components/IncrementalLoadingCollection/src/CommunityToolkit.WinUI.Controls.IncrementalLoadingCollection.csproj b/components/IncrementalLoadingCollection/src/CommunityToolkit.WinUI.Controls.IncrementalLoadingCollection.csproj deleted file mode 100644 index 98ba449d..00000000 --- a/components/IncrementalLoadingCollection/src/CommunityToolkit.WinUI.Controls.IncrementalLoadingCollection.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - IncrementalLoadingCollection - This package contains IncrementalLoadingCollection. - 0.0.1 - - - CommunityToolkit.WinUI.Controls.IncrementalLoadingCollectionRns - - - - - From d88d44464504ee2c2fbfd1ed1c31ebc83fe28b8e Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 30 May 2023 19:53:33 +0200 Subject: [PATCH 5/9] Remove unused test files --- ...leIncrementalLoadingCollectionTestClass.cs | 134 ------------------ ...eIncrementalLoadingCollectionTestPage.xaml | 14 -- ...crementalLoadingCollectionTestPage.xaml.cs | 16 --- 3 files changed, 164 deletions(-) delete mode 100644 components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestClass.cs delete mode 100644 components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml delete mode 100644 components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml.cs diff --git a/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestClass.cs b/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestClass.cs deleted file mode 100644 index 30007f43..00000000 --- a/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestClass.cs +++ /dev/null @@ -1,134 +0,0 @@ -// 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. - -using CommunityToolkit.Tooling.TestGen; -using CommunityToolkit.Tests; -using CommunityToolkit.WinUI.Controls; - -namespace IncrementalLoadingCollectionExperiment.Tests; - -[TestClass] -public partial class ExampleIncrementalLoadingCollectionTestClass : VisualUITestBase -{ - // If you don't need access to UI objects directly or async code, use this pattern. - [TestMethod] - public void SimpleSynchronousExampleTest() - { - var assembly = typeof(IncrementalLoadingCollection).Assembly; - var type = assembly.GetType(typeof(IncrementalLoadingCollection).FullName ?? string.Empty); - - Assert.IsNotNull(type, "Could not find IncrementalLoadingCollection type."); - Assert.AreEqual(typeof(IncrementalLoadingCollection), type, "Type of IncrementalLoadingCollection does not match expected type."); - } - - // If you don't need access to UI objects directly, use this pattern. - [TestMethod] - public async Task SimpleAsyncExampleTest() - { - await Task.Delay(250); - - Assert.IsTrue(true); - } - - // Example that shows how to check for exception throwing. - [TestMethod] - public void SimpleExceptionCheckTest() - { - // If you need to check exceptions occur for invalid inputs, etc... - // Use Assert.ThrowsException to limit the scope to where you expect the error to occur. - // Otherwise, using the ExpectedException attribute could swallow or - // catch other issues in setup code. - Assert.ThrowsException(() => throw new NotImplementedException()); - } - - // The UIThreadTestMethod automatically dispatches to the UI for us to work with UI objects. - [UIThreadTestMethod] - public void SimpleUIAttributeExampleTest() - { - var component = new IncrementalLoadingCollection(); - Assert.IsNotNull(component); - } - - // The UIThreadTestMethod can also easily grab a XAML Page for us by passing its type as a parameter. - // This lets us actually test a control as it would behave within an actual application. - // The page will already be loaded by the time your test is called. - [UIThreadTestMethod] - public void SimpleUIExamplePageTest(ExampleIncrementalLoadingCollectionTestPage page) - { - // You can use the Toolkit Visual Tree helpers here to find the component by type or name: - var component = page.FindDescendant(); - - Assert.IsNotNull(component); - - var componentByName = page.FindDescendant("IncrementalLoadingCollectionControl"); - - Assert.IsNotNull(componentByName); - } - - // You can still do async work with a UIThreadTestMethod as well. - [UIThreadTestMethod] - public async Task SimpleAsyncUIExamplePageTest(ExampleIncrementalLoadingCollectionTestPage page) - { - // This helper can be used to wait for a rendering pass to complete. - // Note, this is already done by loading a Page with the [UIThreadTestMethod] helper. - await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { }); - - var component = page.FindDescendant(); - - Assert.IsNotNull(component); - } - - //// ----------------------------- ADVANCED TEST SCENARIOS ----------------------------- - - // If you need to use DataRow, you can use this pattern with the UI dispatch still. - // Otherwise, checkout the UIThreadTestMethod attribute above. - // See https://github.com/CommunityToolkit/Labs-Windows/issues/186 - [TestMethod] - public async Task ComplexAsyncUIExampleTest() - { - await EnqueueAsync(() => - { - var component = new IncrementalLoadingCollection_ClassicBinding(); - Assert.IsNotNull(component); - }); - } - - // If you want to load other content not within a XAML page using the UIThreadTestMethod above. - // Then you can do that using the Load/UnloadTestContentAsync methods. - [TestMethod] - public async Task ComplexAsyncLoadUIExampleTest() - { - await EnqueueAsync(async () => - { - var component = new IncrementalLoadingCollection_ClassicBinding(); - Assert.IsNotNull(component); - Assert.IsFalse(component.IsLoaded); - - await LoadTestContentAsync(component); - - Assert.IsTrue(component.IsLoaded); - - await UnloadTestContentAsync(component); - - Assert.IsFalse(component.IsLoaded); - }); - } - - // You can still use the UIThreadTestMethod to remove the extra layer for the dispatcher as well: - [UIThreadTestMethod] - public async Task ComplexAsyncLoadUIExampleWithoutDispatcherTest() - { - var component = new IncrementalLoadingCollection_ClassicBinding(); - Assert.IsNotNull(component); - Assert.IsFalse(component.IsLoaded); - - await LoadTestContentAsync(component); - - Assert.IsTrue(component.IsLoaded); - - await UnloadTestContentAsync(component); - - Assert.IsFalse(component.IsLoaded); - } -} diff --git a/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml b/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml deleted file mode 100644 index 2fae6ef0..00000000 --- a/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - diff --git a/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml.cs b/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml.cs deleted file mode 100644 index e72815f8..00000000 --- a/components/IncrementalLoadingCollection/tests/ExampleIncrementalLoadingCollectionTestPage.xaml.cs +++ /dev/null @@ -1,16 +0,0 @@ -// 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. - -namespace IncrementalLoadingCollectionExperiment.Tests; - -/// -/// An empty page that can be used on its own or navigated to within a Frame. -/// -public sealed partial class ExampleIncrementalLoadingCollectionTestPage : Page -{ - public ExampleIncrementalLoadingCollectionTestPage() - { - this.InitializeComponent(); - } -} From 744e6aba53329b3f04980237ca4d19eae3fbd5c9 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 30 May 2023 19:54:48 +0200 Subject: [PATCH 6/9] Adding samples --- .../samples/Assets/AppIcon.png | Bin 0 -> 1348 bytes ...ncrementalLoadingCollection.Samples.csproj | 8 + .../samples/IncrementalLoadingCollection.md | 93 +++- .../IncrementalLoadingCollectionSample.xaml | 69 +++ ...IncrementalLoadingCollectionSample.xaml.cs | 32 ++ .../samples/PeopleSource.cs | 67 +++ .../samples/Person.cs | 16 + ....WinUI.IncrementalLoadingCollection.csproj | 13 + .../src/IIncrementalSource.cs | 29 ++ .../src/IncrementalLoadingCollection.cs | 493 +++++++++--------- .../tests/DataSource.cs | 53 ++ ...crementalLoadingCollection.Tests.projitems | 16 +- .../Test_IncrementalLoadingCollection.cs | 143 +++++ 13 files changed, 765 insertions(+), 267 deletions(-) create mode 100644 components/IncrementalLoadingCollection/samples/Assets/AppIcon.png create mode 100644 components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionSample.xaml create mode 100644 components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionSample.xaml.cs create mode 100644 components/IncrementalLoadingCollection/samples/PeopleSource.cs create mode 100644 components/IncrementalLoadingCollection/samples/Person.cs create mode 100644 components/IncrementalLoadingCollection/src/CommunityToolkit.WinUI.IncrementalLoadingCollection.csproj create mode 100644 components/IncrementalLoadingCollection/src/IIncrementalSource.cs create mode 100644 components/IncrementalLoadingCollection/tests/DataSource.cs create mode 100644 components/IncrementalLoadingCollection/tests/Test_IncrementalLoadingCollection.cs diff --git a/components/IncrementalLoadingCollection/samples/Assets/AppIcon.png b/components/IncrementalLoadingCollection/samples/Assets/AppIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..0967494781504a0fe3e1205b2717db6816ce6c0a GIT binary patch literal 1348 zcmV-K1-tr*P)`^ zT~!oa`+nYgO=9wDQ2U9=6YIo*jS5bj#Ht`ptfFHZ5m8!ficYEpQ$#I@n6^$7u`hz7 zD8xZ2{%AxiWS~Y+u+-WF`!y|n&8KP2yXSb$KA(Hdm7q)HR`E)>XNIjOZTp}@nAlmrO8#|^Nw}R};z~Eq61+Ok^a4m#hh-7y-2HEoRj!GQSpQ;KKGy_oD{7sL(kI(#6?xJZ38J0_%_jh}(OHI5l zG*d|5$IqSUzP)e;LdSXHlOAnU65)l~Xn#%bQ~dnb`v`KAZj_W#-6Tj~q~3+P+jU;^ zI>Pi$3KK=&oh{5=W}e*@WcC0tFhBC_?udh1Mq&ETnQ|j zp!(_M7D$;`hp>o+cOabuSoGP98Uj*gjqMtA(*zALDJ=)Gi!4IeVBpMB#AyI(bnbMJ zZ-WEl{F95STPUOj&`?U_&}2{6ICbKJrsefu95~C7d}WYno8gayNjNfu%9BN%SqwjS zH#$~CZ1}^Gvv_;|5%An{07RzD+02s!A8olG?JY5@efi^G_~5I<9)u^gJta|=DBe+W z8IRs|Gn`1Fc#_nWhcMIU3?Vbb?Oqw-k$cv9TS^x3;TOLu4Qq=$FQT}lk?L}%B^H-1 zA}c`@AS^1qFiKAc^4tiuYt5fn5kfyM zVV`ge3tFSAxhmO+PR`aboyiibTMIhylDwJS%o>z9~S2vv4rT%z}<{!z#m84|@Ys1%Y5sOK2jlVs0;G1R|{ zE!g$jtRlbqAWJw6hI|<8d=GaP zj>>$WjZ4Q4zeee^^~HLt8BkSB9Cj}1PmA*S-N;}dXZaE~MY5$DSged=U zO-n^xLxClz^kNFY%mEivpKk*OE?J?2YbEl-cd6jj0~b)66Bzom#+hpASruVy zcNecuDlpYoR$<`D4h-y>&a^ZpeoBil&IH6mIPdui`z7h5h1RKZ~NSDBUd3Eq@Hw{-_ z>ioD5S1Gzp + + + + + + PreserveNewest + + diff --git a/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollection.md b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollection.md index 454fe6a9..e41673f1 100644 --- a/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollection.md +++ b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollection.md @@ -1,8 +1,8 @@ --- title: IncrementalLoadingCollection -author: githubaccount -description: TODO: Your experiment's description here -keywords: IncrementalLoadingCollection, Control, Layout +author: nmetulev +description: The IncrementalLoadingCollection helpers greatly simplify the definition and usage of collections whose items can be loaded incrementally only when needed by the view +keywords: IncrementalLoadingCollection, Control, Data, Incremental, Loading dev_langs: - csharp category: Controls @@ -11,22 +11,85 @@ discussion-id: 0 issue-id: 0 --- - - - - - +# Incremental Loading Collection Helpers - +The **IncrementalLoadingCollection** helpers greatly simplify the definition and usage of collections whose items can be loaded incrementally only when needed by the view, i.e., when user scrolls a [ListView](/uwp/api/Windows.UI.Xaml.Controls.ListView) or a [GridView](/uwp/api/Windows.UI.Xaml.Controls.GridView). -# IncrementalLoadingCollection +> [!Sample IncrementalLoadingCollectionSample] -TODO: Fill in information about this experiment and how to get started here... +[IIncrementalSource](/dotnet/api/microsoft.toolkit.collections.iincrementalsource-1) - An interface that represents a data source whose items can be loaded incrementally. -## Custom Control +[IncrementalLoadingCollection](/dotnet/api/microsoft.toolkit.uwp.incrementalloadingcollection-2) - An extension of [ObservableCollection](/dotnet/api/system.collections.objectmodel.observablecollection-1) such that its items are loaded only when needed. -You can inherit from an existing component as well, like `Panel`, this example shows a control without a -XAML Style that will be more light-weight to consume by an app developer: +## IncrementalLoadingCollection Properties -> [!Sample IncrementalLoadingCollectionCustomSample] +| Property | Type | Description | +| -- | -- | -- | +| CurrentPageIndex | int | Gets or sets a value indicating The zero-based index of the current items page | +| HasMoreItems | bool | Gets a value indicating whether the collection contains more items to retrieve | +| IsLoading | bool | Gets a value indicating whether new items are being loaded | +| ItemsPerPage | int | Gets a value indicating how many items that must be retrieved for each incremental call | +| OnEndLoading | [Action](/dotnet/api/system.action) | Gets or sets an Action that is called when a retrieval operation ends | +| OnError | Action\ | Gets or sets an Action that is called if an error occours during data retrieval. The actual Exception is passed as an argument | +| OnStartLoading | Action | Gets or sets an Action that is called when a retrieval operation begins | +## IncrementalLoadingCollection Methods + +| Methods | Return Type | Description | +| -- | -- | -- | +| LoadDataAsync(CancellationToken) | Task> | Actually performs the incremental loading | +| LoadMoreItemsAsync(UInt32) | IAsyncOperation\ | Initializes incremental loading from the view | +| Refresh() | void | Clears the collection and resets the page index which triggers an automatic reload of the first page | +| RefreshAsync() | Task | Clears the collection and reloads data from the source | + +## Example + +`IIncrementalSource` allows to define the data source: + +```csharp +// Be sure to include the using at the top of the file: +//using CommunityToolkit.WinUI; + +public class Person +{ + public string Name { get; set; } +} + +public class PeopleSource : IIncrementalSource +{ + private readonly List people; + + public PeopleSource() + { + // Creates an example collection. + people = new List(); + + for (int i = 1; i <= 200; i++) + { + var p = new Person { Name = "Person " + i }; + people.Add(p); + } + } + + public async Task> GetPagedItemsAsync(int pageIndex, int pageSize) + { + // Gets items from the collection according to pageIndex and pageSize parameters. + var result = (from p in people + select p).Skip(pageIndex * pageSize).Take(pageSize); + + // Simulates a longer request... + await Task.Delay(1000); + + return result; + } +} +``` + +The *GetPagedItemsAsync* method is invoked every time the view need to show more items. + +`IncrementalLoadingCollection` can then be bound to a [ListView](/uwp/api/Windows.UI.Xaml.Controls.ListView) or a [GridView-like](/uwp/api/Windows.UI.Xaml.Controls.GridView) control: + +```csharp +var collection = new IncrementalLoadingCollection(); +PeopleListView.ItemsSource = collection; +``` diff --git a/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionSample.xaml b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionSample.xaml new file mode 100644 index 00000000..e1b4438f --- /dev/null +++ b/components/IncrementalLoadingCollection/samples/IncrementalLoadingCollectionSample.xaml @@ -0,0 +1,69 @@ + + + + + + + + + + + + +