diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index c8987eb9..6935449f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,8 +1,5 @@ -# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.208.0/containers/dotnet/.devcontainer/base.Dockerfile - -# [Choice] .NET version: 6.0, 5.0, 3.1, 6.0-bullseye, 5.0-bullseye, 3.1-bullseye, 6.0-focal, 5.0-focal, 3.1-focal -ARG VARIANT="6.0-bullseye-slim" -FROM mcr.microsoft.com/vscode/devcontainers/dotnet:0-${VARIANT} +# See https://github.com/devcontainers/images/tree/main/src/dotnet for image choices +FROM mcr.microsoft.com/vscode/devcontainers/dotnet:9.0 # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 ARG NODE_VERSION="none" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 61655025..da7c1a47 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,7 @@ "args": { // Update 'VARIANT' to pick a .NET Core version: 3.1, 5.0, 6.0 // Append -bullseye or -focal to pin to an OS version. - "VARIANT": "8.0", + "VARIANT": "9.0", // Options "NODE_VERSION": "lts/*" } diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3d4c0a45..fb852cfb 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -20,4 +20,4 @@ contact_links: about: "The Windows Community Toolkit uses Uno Platform for cross-platform WinUI. See the Uno Platform repo for issues specific to running on non-Windows platforms:" - name: Discord url: https://aka.ms/wct/discord - about: "Join the UWP Discord Server and talk to us in the #community-toolkit channel." + about: "Join the Windows App Community Discord Server and talk to us in the #community-toolkit channel." diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f9ee19c..f5b6a945 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,19 +8,17 @@ name: CI on: # Triggers the workflow on push or pull request events but only for the main or release branches push: - branches: [ main, 'rel/*' ] + branches: [ main, 'rel/*', 'dev/*' ] pull_request: - branches: [ main ] + branches: [ main, 'dev/*' ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: merge_group: env: - DOTNET_VERSION: ${{ '8.0.201' }} - # Debug logging enabled via GitHub UI during reruns or set as repo secret. - # See also https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging - ENABLE_DIAGNOSTICS: ${{ runner.debug }} + DOTNET_VERSION: ${{ '9.0.x' }} + ENABLE_DIAGNOSTICS: true MSBUILD_VERBOSITY: normal #COREHOST_TRACE: 1 COREHOST_TRACEFILE: corehosttrace.log @@ -59,19 +57,21 @@ jobs: # Build both Uno.UI/WinUI2/UWP and Uno.WinUI/WinUI3/WindowsAppSDK versions of our packages using a matrix build: needs: [Xaml-Style-Check] - runs-on: windows-latest-large + runs-on: windows-latest # See https://docs.github.com/actions/using-jobs/using-a-matrix-for-your-jobs strategy: - fail-fast: false # prevent one matrix pipeline from being cancelled if one fails, we want them both to run to completion. + fail-fast: false # prevent one matrix pipeline from being cancelled if one fails, we want them all to run to completion. matrix: - platform: [WinUI2, WinUI3] - - env: - # faux-ternary expression to select which platforms to build for each platform vs. duplicating step below. - TARGET_PLATFORMS: ${{ matrix.platform != 'WinUI3' && 'all-wasdk' || 'all-uwp' }} - TEST_PLATFORM: ${{ matrix.platform != 'WinUI3' && 'UWP' || 'WinAppSdk' }} - VERSION_PROPERTY: ${{ github.ref == 'refs/heads/main' && format('build.{0}', github.run_number) || format('pull-{0}.{1}', github.event.number, github.run_number) }} + winui: [2, 3] + multitarget: ['uwp', 'wasdk', 'wasm', 'wpf', 'linuxgtk', 'macos', 'ios', 'android'] + exclude: + # WinUI 2 not supported on wasdk + - winui: 2 + multitarget: wasdk + # WinUI 3 not supported on uwp + - winui: 3 + multitarget: uwp # Steps represent a sequence of tasks that will be executed as part of the job steps: @@ -111,9 +111,11 @@ jobs: run: dotnet tool restore - name: Run Uno Check to Install Dependencies + if: ${{ matrix.multitarget != 'wasdk' && matrix.multitarget != 'linuxgtk' && matrix.multitarget != 'wpf' }} run: > dotnet tool run uno-check --ci + --target ${{ matrix.multitarget }} --fix --non-interactive --skip wsl @@ -126,58 +128,21 @@ jobs: with: vs-version: '[17.9,)' - - name: Enable ${{ env.TARGET_PLATFORMS }} TargetFrameworks - working-directory: ./${{ env.MULTI_TARGET_DIRECTORY }} - run: powershell -version 5.1 -command "./UseTargetFrameworks.ps1 ${{ env.TARGET_PLATFORMS }}" -ErrorAction Stop - - - name: Generate solution w/ ${{ env.TEST_PLATFORM }} Tests + # Generate full solution with all projects (sample gallery heads, components, tests) + - name: Generate solution with ${{ matrix.multitarget }} gallery, components and tests working-directory: ./ - run: powershell -version 5.1 -command "./tooling/GenerateAllSolution.ps1 -IncludeHeads ${{ env.TEST_PLATFORM }}${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }}" -ErrorAction Stop - - - name: Enable Uno.WinUI (in WinUI3 matrix only) - if: ${{ matrix.platform == 'WinUI3' }} - working-directory: ./${{ env.MULTI_TARGET_DIRECTORY }} - run: powershell -version 5.1 -command "./UseUnoWinUI.ps1 3" -ErrorAction Stop - - - name: Format Date/Time of Commit for Package Version - if: ${{ env.IS_RELEASE == 'false' }} - run: | - echo "VERSION_DATE=$(git log -1 --format=%cd --date=format:%y%m%d)" >> $env:GITHUB_ENV - - # Semver regex: https://regex101.com/r/Ly7O1x/3/ - - name: Format Date/Time of Release Package Version - if: ${{ env.IS_RELEASE == 'true' }} - run: | - $ref = "${{ github.ref }}" - $ref -match "^refs/heads/rel/(?0|[1-9]\d*)\.(?0|[1-9]\d*)\.(?0|[1-9]\d*)(?:-(?(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" - echo "VERSION_DATE=$($matches['patch'])" >> $env:GITHUB_ENV - echo "VERSION_PROPERTY=$($matches['prerelease'])" >> $env:GITHUB_ENV + run: powershell -version 5.1 -command "./tooling/GenerateAllSolution.ps1 -MultiTargets ${{ matrix.multitarget }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} -WinUIMajorVersion ${{ matrix.winui }}" -ErrorAction Stop + # Build solution - name: MSBuild run: > msbuild.exe /restore /nowarn:MSB4011 /p:Configuration=Release /m - /p:DateForVersion=${{ env.VERSION_DATE }} - /p:PreviewVersion=${{ env.VERSION_PROPERTY }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '/bl' || '' }} /v:${{ env.MSBUILD_VERBOSITY }} CommunityToolkit.AllComponents.sln - # Build All Packages - - name: pack experiments - working-directory: ./tooling/Scripts/ - run: ./PackEachExperiment.ps1 -date ${{ env.VERSION_DATE }}${{ env.VERSION_PROPERTY != '' && format(' -postfix {0}', env.VERSION_PROPERTY) || '' }} - - # Push Pull Request Packages to our DevOps Artifacts Feed (see nuget.config) - - name: Push Pull Request Packages (if not fork) - if: ${{ env.IS_PR == 'true' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' }} - run: | - dotnet nuget add source https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-PullRequests/nuget/v3/index.json ` - --name PullRequests ` - --username dummy --password ${{ secrets.DEVOPS_PACKAGE_PUSH_TOKEN }} - dotnet nuget push "**/*.nupkg" --api-key dummy --source PullRequests --skip-duplicate - # Run tests - name: Setup VSTest Path uses: darenm/setup-vstest@3a16d909a1f3bbc65b52f8270d475d905e7d3e44 @@ -191,20 +156,21 @@ jobs: id: test-generator run: vstest.console.exe ./tooling/CommunityToolkit.Tooling.SampleGen.Tests/bin/Release/net6.0/CommunityToolkit.Tooling.SampleGen.Tests.dll /logger:"trx;LogFileName=SourceGenerators.trx" - - name: Run experiment tests against ${{ env.TEST_PLATFORM }} + - name: Run component tests against ${{ matrix.multitarget }} + if: ${{ matrix.multitarget == 'uwp' || matrix.multitarget == 'wasdk' }} id: test-platform - run: vstest.console.exe ./tooling/**/CommunityToolkit.Tests.${{ env.TEST_PLATFORM }}.build.appxrecipe /Framework:FrameworkUap10 /logger:"trx;LogFileName=${{ env.TEST_PLATFORM }}.trx" /Blame + run: vstest.console.exe ./tooling/**/CommunityToolkit.Tests.${{ matrix.multitarget }}.build.appxrecipe /Framework:FrameworkUap10 /logger:"trx;LogFileName=${{ matrix.multitarget }}.trx" /Blame - name: Create test reports run: | - testspace '[${{ matrix.platform }}]./TestResults/*.trx' - if: ${{ always() && (steps.test-generator.conclusion == 'success' || steps.test-platform.conclusion == 'success') }} + testspace '[${{ matrix.multitarget }}]./TestResults/*.trx' + if: ${{ (matrix.multitarget == 'uwp' || matrix.multitarget == 'wasdk') && (steps.test-generator.conclusion == 'success' || steps.test-platform.conclusion == 'success') }} - name: Artifact - Diagnostic Logs uses: actions/upload-artifact@v4 if: ${{ (env.ENABLE_DIAGNOSTICS == 'true' || env.COREHOST_TRACE != '') && always() }} with: - name: build-logs-${{ matrix.platform }} + name: build-logs-${{ matrix.multitarget }}-winui${{ matrix.winui }} path: ./**/*.*log - name: Artifact - ILC Repro @@ -226,7 +192,7 @@ jobs: uses: actions/upload-artifact@v4 if: ${{ (env.ENABLE_DIAGNOSTICS == 'true' || env.COREHOST_TRACE != '') && always() }} with: - name: CrashDumps-${{ matrix.platform }} + name: CrashDumps-${{ matrix.multitarget }}-winui${{ matrix.winui }} path: '${{ github.workspace }}/CrashDumps' - name: Analyze Dump @@ -235,11 +201,94 @@ jobs: dotnet tool install --global dotnet-dump dotnet-dump analyze ${{ steps.detect-dump.outputs.DUMP_FILE }} -c "clrstack" -c "pe -lines" -c "exit" + package: + runs-on: windows-latest + needs: [build] + strategy: + fail-fast: false # prevent one matrix pipeline from being cancelled if one fails, we want them all to run to completion. + matrix: + winui: [2, 3] + + env: + VERSION_PROPERTY: ${{ github.ref == 'refs/heads/main' && format('build.{0}', github.run_number) || format('pull-{0}.{1}', github.event.number, github.run_number) }} + + steps: + - name: Install .NET SDK v${{ env.DOTNET_VERSION }} + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: .NET Info (if diagnostics) + if: ${{ env.ENABLE_DIAGNOSTICS == 'true' }} + run: dotnet --info + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - name: Checkout Repository + uses: actions/checkout@v4 + with: + submodules: recursive + + # Semver regex: https://regex101.com/r/Ly7O1x/3/ + - name: Format Date/Time of Release Package Version + if: ${{ env.IS_RELEASE == 'true' }} + run: | + $ref = "${{ github.ref }}" + $ref -match "^refs/heads/rel/(?0|[1-9]\d*)\.(?0|[1-9]\d*)\.(?0|[1-9]\d*)(?:-(?(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" + echo "VERSION_DATE=$($matches['patch'])" >> $env:GITHUB_ENV + echo "VERSION_PROPERTY=$($matches['prerelease'])" >> $env:GITHUB_ENV + + - name: Format Date/Time of Commit for Package Version + if: ${{ env.IS_RELEASE == 'false' }} + run: | + echo "VERSION_DATE=$(git log -1 --format=%cd --date=format:%y%m%d)" >> $env:GITHUB_ENV + + - name: Restore dotnet tools + run: dotnet tool restore + + - name: Run Uno Check to Install Dependencies + run: > + dotnet tool run uno-check + --ci + --fix + --non-interactive + --skip wsl + --skip androidemulator + --skip vswinworkloads + --verbose + + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v2 + with: + vs-version: '[17.9,)' + + - name: Define excluded MultiTargets (WinUI 2) + if: ${{ matrix.winui == '2' }} + run: | + echo "EXCLUDED_MULTITARGETS=wasdk" >> $env:GITHUB_ENV + + - name: Define excluded MultiTargets (WinUI 3) + if: ${{ matrix.winui == '3' }} + run: | + echo "EXCLUDED_MULTITARGETS=uwp" >> $env:GITHUB_ENV + + # Build and pack component nupkg + - name: Build and pack component packages + run: ./tooling/Build-Toolkit-Components.ps1 -MultiTargets all -ExcludeMultiTargets ${{ env.EXCLUDED_MULTITARGETS }} -WinUIMajorVersion ${{ matrix.winui }} -DateForVersion ${{ env.VERSION_DATE }} ${{ env.VERSION_PROPERTY != '' && format('-PreviewVersion "{0}"', env.VERSION_PROPERTY) || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-EnableBinlogs' || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-Verbose' || '' }} -BinlogOutput ./ -NupkgOutput ./ -Release + + # Push Pull Request Packages to our DevOps Artifacts Feed (see nuget.config) + - name: Push Pull Request Packages (if not fork) + if: ${{ env.IS_PR == 'true' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' }} + run: | + dotnet nuget add source https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-PullRequests/nuget/v3/index.json ` + --name PullRequests ` + --username dummy --password ${{ secrets.DEVOPS_PACKAGE_PUSH_TOKEN }} + dotnet nuget push "*.nupkg" --api-key dummy --source PullRequests --skip-duplicate + - name: Upload Package List uses: actions/upload-artifact@v4 if: ${{ env.IS_PR == 'false' }} with: - name: nuget-list-${{ matrix.platform }} + name: nuget-list-${{ matrix.winui }} if-no-files-found: error path: | ${{ github.workspace }}/.github/workflows/SignClientFileList.txt @@ -249,13 +298,20 @@ jobs: uses: actions/upload-artifact@v4 if: ${{ env.IS_PR == 'false' || github.event.pull_request.head.repo.full_name != github.repository }} with: - name: nuget-packages-${{ matrix.platform }} + name: nuget-packages-winui${{ matrix.winui }} if-no-files-found: error path: | - **/*.nupkg + ./*.nupkg + + - name: Artifact - Diagnostic Logs + uses: actions/upload-artifact@v4 + if: ${{ (env.ENABLE_DIAGNOSTICS == 'true' || env.COREHOST_TRACE != '') && always() }} + with: + name: build-logs-winui${{ matrix.winui }} + path: ./*.*log sign: - needs: [build] + needs: [package] if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/rel/') }} runs-on: windows-latest permissions: @@ -264,7 +320,7 @@ jobs: strategy: fail-fast: false # prevent one matrix pipeline from being cancelled if one fails, we want them both to run to completion. matrix: - platform: [WinUI2, WinUI3] + winui: [2, 3] steps: - name: Install .NET SDK v${{ env.DOTNET_VERSION }} @@ -275,13 +331,13 @@ jobs: - name: Download Package List uses: actions/download-artifact@v4 with: - name: nuget-list-${{ matrix.platform }} + name: nuget-list-${{ matrix.winui }} path: ./ - - name: Download built packages for ${{ matrix.platform }} + - name: Download built packages for WinUI ${{ matrix.winui }} uses: actions/download-artifact@v4 with: - name: nuget-packages-${{ matrix.platform }} + name: nuget-packages-winui${{ matrix.winui }} path: ./packages - name: Install Signing Tool @@ -315,7 +371,7 @@ jobs: uses: actions/upload-artifact@v4 if: ${{ env.IS_RELEASE == 'true' }} with: - name: signed-nuget-packages-${{ matrix.platform }} + name: signed-nuget-packages-${{ matrix.winui }} if-no-files-found: error path: | ${{ github.workspace }}/packages/**/*.nupkg @@ -329,7 +385,7 @@ jobs: strategy: fail-fast: false # prevent one matrix pipeline from being cancelled if one fails, we want them both to run to completion. matrix: - platform: [WinUI2, WinUI3] + winui: [2, 3] steps: - name: Install .NET SDK v${{ env.DOTNET_VERSION }} @@ -337,10 +393,10 @@ jobs: with: dotnet-version: ${{ env.DOTNET_VERSION }} - - name: Download signed packages for ${{ matrix.platform }} + - name: Download signed packages for WinUI ${{ matrix.winui }} uses: actions/download-artifact@v4 with: - name: signed-nuget-packages-${{ matrix.platform }} + name: signed-nuget-packages-${{ matrix.winui }} path: ./packages - name: Push to NuGet.org diff --git a/.vscode/settings.json b/.vscode/settings.json index 45b1618d..444dd1ee 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { - "omnisharp.defaultLaunchSolution": "CommunityToolkit.AllComponents.sln", "omnisharp.disableMSBuildDiagnosticWarning": true, "omnisharp.enableRoslynAnalyzers": true, "omnisharp.useGlobalMono": "never", @@ -8,5 +7,6 @@ "csharp.suppressDotnetRestoreNotification": true, "csharp.semanticHighlighting.enabled": true, "omnisharp.enableMsBuildLoadProjectsOnDemand": true, - "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true + "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true, + "dotnet.defaultSolution": "CommunityToolkit.AllComponents.sln" } \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 51c3f6dd..7eb72bb7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ 8 - 1 + 2 CommunityToolkit $([MSBuild]::EnsureTrailingSlash('$(MSBuildThisFileDirectory)')) @@ -26,7 +26,11 @@ true $(NoWarn);Uno0001 - $(NoWarn);TKSMPL0014 + $(NoWarn);TKSMPL0014; + + + NU1901;NU1902;NU1903;NU1904 + $(NoWarn);TKSMPL0014; diff --git a/Directory.Build.targets b/Directory.Build.targets index e3c75c2c..deeb1665 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,7 +1,11 @@ + + + + - + diff --git a/GenerateAllSolution.bat b/GenerateAllSolution.bat index e7d3e989..61551e18 100644 --- a/GenerateAllSolution.bat +++ b/GenerateAllSolution.bat @@ -1,5 +1,5 @@ @ECHO OFF -SET "IncludeHeads=%1" -IF "%IncludeHeads%"=="" SET "IncludeHeads=all" +SET "MultiTargets=%1" +IF "%MultiTargets%"=="" SET "MultiTargets=uwp,wasdk,wasm" -powershell .\tooling\GenerateAllSolution.ps1 -IncludeHeads %IncludeHeads% \ No newline at end of file +powershell .\tooling\GenerateAllSolution.ps1 -MultiTargets %MultiTargets% \ No newline at end of file diff --git a/ReadMe.md b/ReadMe.md index be916f68..45c10824 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -13,10 +13,12 @@ _Building something cool? Want to engage with other developers? Want to contribu ### [Try out our Sample Gallery from the Microsoft Store](https://aka.ms/windowstoolkitapp) -Want to see the toolkit in action before jumping into the code? Download and play with the [Windows Community Toolkit Gallery](https://www.microsoft.com/store/apps/9nblggh4tlcq) from the Store. +Want to see the toolkit in action before jumping into the code? Download and play with the [Windows Community Toolkit Gallery](https://aka.ms/windowstoolkitapp) from the Store. Please read the [Getting Started with the Windows Community Toolkit](https://docs.microsoft.com/dotnet/communitytoolkit/windows/getting-started) page for more detailed information about using the toolkit. +If you're updating from a pre-8.x version of the Windows Community Toolkit, see [our migration notes here](https://aka.ms/toolkit/windows/migration). + ### Windows Community Toolkit Labs Have an idea for a new feature? Want to checkout the latest things being built. _[Then head over to Windows Community Toolkit Labs](https://aka.ms/toolkit/labs/windows)_. @@ -34,9 +36,11 @@ git clone --recurse-submodules https://github.com/CommunityToolkit/Windows.git ## Build Requirements - Visual Studio 2022 (UWP & Desktop Workloads for .NET) -- .NET 6 SDK -- Windows App SDK -- Windows SDK 19041 +- .NET 8 SDK +- Windows 10 SDK, version 2004 (10.0.19041.0) +- Windows 10 21H1 (Build 19043) or greater +- Run `dotnet tool restore` from the project root to install SlnGen +- Run build scripts from the [Developer Command Prompt for Visual Studio](https://learn.microsoft.com/visualstudio/ide/reference/command-prompt-powershell) or from elsewhere after adding `MSBuild.exe` to your PATH ## 🚀 Contribution diff --git a/Windows.Toolkit.Common.props b/Windows.Toolkit.Common.props index 8853b027..207061c1 100644 --- a/Windows.Toolkit.Common.props +++ b/Windows.Toolkit.Common.props @@ -29,8 +29,4 @@ true true - - - - diff --git a/components/Animations/src/Expressions/ExpressionNodes/BooleanNode.cs b/components/Animations/src/Expressions/ExpressionNodes/BooleanNode.cs index 552c6a74..69415ed0 100644 --- a/components/Animations/src/Expressions/ExpressionNodes/BooleanNode.cs +++ b/components/Animations/src/Expressions/ExpressionNodes/BooleanNode.cs @@ -10,7 +10,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class BooleanNode. This class cannot be inherited. /// /// -public sealed class BooleanNode : ExpressionNode +public sealed partial class BooleanNode : ExpressionNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ExpressionNodes/ColorNode.cs b/components/Animations/src/Expressions/ExpressionNodes/ColorNode.cs index 8e8cb601..9bf4a263 100644 --- a/components/Animations/src/Expressions/ExpressionNodes/ColorNode.cs +++ b/components/Animations/src/Expressions/ExpressionNodes/ColorNode.cs @@ -12,7 +12,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class ColorNode. This class cannot be inherited. /// /// -public sealed class ColorNode : ExpressionNode +public sealed partial class ColorNode : ExpressionNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ExpressionNodes/Matrix3x2Node.cs b/components/Animations/src/Expressions/ExpressionNodes/Matrix3x2Node.cs index efbf7830..bf1768c7 100644 --- a/components/Animations/src/Expressions/ExpressionNodes/Matrix3x2Node.cs +++ b/components/Animations/src/Expressions/ExpressionNodes/Matrix3x2Node.cs @@ -13,7 +13,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class Matrix3x2Node. This class cannot be inherited. /// /// -public sealed class Matrix3x2Node : ExpressionNode +public sealed partial class Matrix3x2Node : ExpressionNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ExpressionNodes/QuaternionNode.cs b/components/Animations/src/Expressions/ExpressionNodes/QuaternionNode.cs index 3855712e..03687905 100644 --- a/components/Animations/src/Expressions/ExpressionNodes/QuaternionNode.cs +++ b/components/Animations/src/Expressions/ExpressionNodes/QuaternionNode.cs @@ -12,7 +12,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class QuaternionNode. This class cannot be inherited. /// /// -public sealed class QuaternionNode : ExpressionNode +public sealed partial class QuaternionNode : ExpressionNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ExpressionNodes/ScalarNode.cs b/components/Animations/src/Expressions/ExpressionNodes/ScalarNode.cs index a9e34a3c..b6769076 100644 --- a/components/Animations/src/Expressions/ExpressionNodes/ScalarNode.cs +++ b/components/Animations/src/Expressions/ExpressionNodes/ScalarNode.cs @@ -10,7 +10,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class ScalarNode. This class cannot be inherited. /// /// -public sealed class ScalarNode : ExpressionNode +public sealed partial class ScalarNode : ExpressionNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ExpressionNodes/Vector2Node.cs b/components/Animations/src/Expressions/ExpressionNodes/Vector2Node.cs index 03396cc9..b914a6b2 100644 --- a/components/Animations/src/Expressions/ExpressionNodes/Vector2Node.cs +++ b/components/Animations/src/Expressions/ExpressionNodes/Vector2Node.cs @@ -12,7 +12,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class Vector2Node. This class cannot be inherited. /// /// -public sealed class Vector2Node : ExpressionNode +public sealed partial class Vector2Node : ExpressionNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ExpressionNodes/Vector3Node.cs b/components/Animations/src/Expressions/ExpressionNodes/Vector3Node.cs index e90fe38f..571ca765 100644 --- a/components/Animations/src/Expressions/ExpressionNodes/Vector3Node.cs +++ b/components/Animations/src/Expressions/ExpressionNodes/Vector3Node.cs @@ -12,7 +12,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class Vector3Node. This class cannot be inherited. /// /// -public sealed class Vector3Node : ExpressionNode +public sealed partial class Vector3Node : ExpressionNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ExpressionNodes/Vector4Node.cs b/components/Animations/src/Expressions/ExpressionNodes/Vector4Node.cs index 49f8451d..94ae79eb 100644 --- a/components/Animations/src/Expressions/ExpressionNodes/Vector4Node.cs +++ b/components/Animations/src/Expressions/ExpressionNodes/Vector4Node.cs @@ -12,7 +12,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class Vector4Node. This class cannot be inherited. /// /// -public sealed class Vector4Node : ExpressionNode +public sealed partial class Vector4Node : ExpressionNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/AmbientLightReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/AmbientLightReferenceNode.cs index 57d8b193..8cc54dd9 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/AmbientLightReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/AmbientLightReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class AmbientLightReferenceNode. This class cannot be inherited. /// /// -public sealed class AmbientLightReferenceNode : ReferenceNode +public sealed partial class AmbientLightReferenceNode : ReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/ColorBrushReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/ColorBrushReferenceNode.cs index cfb802af..3f639247 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/ColorBrushReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/ColorBrushReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class ColorBrushReferenceNode. This class cannot be inherited. /// /// -public sealed class ColorBrushReferenceNode : ReferenceNode +public sealed partial class ColorBrushReferenceNode : ReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/DistantLightReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/DistantLightReferenceNode.cs index 560d980e..1f59f918 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/DistantLightReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/DistantLightReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class DistantLightReferenceNode. This class cannot be inherited. /// /// -public sealed class DistantLightReferenceNode : ReferenceNode +public sealed partial class DistantLightReferenceNode : ReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/DropShadowReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/DropShadowReferenceNode.cs index 6602249c..fde3b8fc 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/DropShadowReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/DropShadowReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class DropShadowReferenceNode. This class cannot be inherited. /// /// -public sealed class DropShadowReferenceNode : ReferenceNode +public sealed partial class DropShadowReferenceNode : ReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/InsetClipReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/InsetClipReferenceNode.cs index 720414b2..e976f940 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/InsetClipReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/InsetClipReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class InsetClipReferenceNode. This class cannot be inherited. /// /// -public sealed class InsetClipReferenceNode : ReferenceNode +public sealed partial class InsetClipReferenceNode : ReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/InteractionTrackerReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/InteractionTrackerReferenceNode.cs index 867800e3..90f08196 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/InteractionTrackerReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/InteractionTrackerReferenceNode.cs @@ -16,7 +16,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class InteractionTrackerReferenceNode. This class cannot be inherited. /// /// -public sealed class InteractionTrackerReferenceNode : ReferenceNode +public sealed partial class InteractionTrackerReferenceNode : ReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/ManipulationPropertySetReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/ManipulationPropertySetReferenceNode.cs index 377cc6e0..6faa3776 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/ManipulationPropertySetReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/ManipulationPropertySetReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class ManipulationPropertySetReferenceNode. This class cannot be inherited. /// /// -public sealed class ManipulationPropertySetReferenceNode : PropertySetReferenceNode +public sealed partial class ManipulationPropertySetReferenceNode : PropertySetReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/NineGridBrushReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/NineGridBrushReferenceNode.cs index a7f1e96a..097b5996 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/NineGridBrushReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/NineGridBrushReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class NineGridBrushReferenceNode. This class cannot be inherited. /// /// -public sealed class NineGridBrushReferenceNode : ReferenceNode +public sealed partial class NineGridBrushReferenceNode : ReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/PointLightReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/PointLightReferenceNode.cs index e7fbf3ee..4f5974f8 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/PointLightReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/PointLightReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class PointLightReferenceNode. This class cannot be inherited. /// /// -public sealed class PointLightReferenceNode : ReferenceNode +public sealed partial class PointLightReferenceNode : ReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/PointerPositionPropertySetReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/PointerPositionPropertySetReferenceNode.cs index 59b5f2d3..7ecffb5c 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/PointerPositionPropertySetReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/PointerPositionPropertySetReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class PointerPositionPropertySetReferenceNode. This class cannot be inherited. /// /// -public sealed class PointerPositionPropertySetReferenceNode : PropertySetReferenceNode +public sealed partial class PointerPositionPropertySetReferenceNode : PropertySetReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/PropertySetReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/PropertySetReferenceNode.cs index c33e8ce1..40bc3825 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/PropertySetReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/PropertySetReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class PropertySetReferenceNode. /// /// -public class PropertySetReferenceNode : ReferenceNode +public partial class PropertySetReferenceNode : ReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/SpotLightReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/SpotLightReferenceNode.cs index b97b9e2d..6cba9c1c 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/SpotLightReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/SpotLightReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class SpotLightReferenceNode. This class cannot be inherited. /// /// -public sealed class SpotLightReferenceNode : ReferenceNode +public sealed partial class SpotLightReferenceNode : ReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/SurfaceBrushReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/SurfaceBrushReferenceNode.cs index cd180501..51082a9a 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/SurfaceBrushReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/SurfaceBrushReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class SurfaceBrushReferenceNode. This class cannot be inherited. /// /// -public sealed class SurfaceBrushReferenceNode : ReferenceNode +public sealed partial class SurfaceBrushReferenceNode : ReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Expressions/ReferenceNodes/VisualReferenceNode.cs b/components/Animations/src/Expressions/ReferenceNodes/VisualReferenceNode.cs index 171fb4eb..d04538f8 100644 --- a/components/Animations/src/Expressions/ReferenceNodes/VisualReferenceNode.cs +++ b/components/Animations/src/Expressions/ReferenceNodes/VisualReferenceNode.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Animations.Expressions; /// Class VisualReferenceNode. This class cannot be inherited. /// /// -public sealed class VisualReferenceNode : ReferenceNode +public sealed partial class VisualReferenceNode : ReferenceNode { /// /// Initializes a new instance of the class. diff --git a/components/Animations/src/Xaml/AnimationScope.cs b/components/Animations/src/Xaml/AnimationScope.cs index cf7d40ba..d4e9a115 100644 --- a/components/Animations/src/Xaml/AnimationScope.cs +++ b/components/Animations/src/Xaml/AnimationScope.cs @@ -8,7 +8,7 @@ namespace CommunityToolkit.WinUI.Animations; /// A container of elements that can be used to conceptually group animations /// together and to assign shared properties to be applied to all the contained items automatically. /// -public sealed class AnimationScope : DependencyObjectCollection, ITimeline +public sealed partial class AnimationScope : DependencyObjectCollection, ITimeline { /// /// Gets or sets the optional initial delay for the animation. diff --git a/components/Animations/src/Xaml/AnimationSet.cs b/components/Animations/src/Xaml/AnimationSet.cs index 1c717ca2..f49c837d 100644 --- a/components/Animations/src/Xaml/AnimationSet.cs +++ b/components/Animations/src/Xaml/AnimationSet.cs @@ -17,7 +17,7 @@ namespace CommunityToolkit.WinUI.Animations; /// A collection of animations that can be grouped together. This type represents a composite animation /// (such as ) that can be executed on a given element. /// -public sealed class AnimationSet : DependencyObjectCollection +public sealed partial class AnimationSet : DependencyObjectCollection { /// /// A conditional weak table storing instances associated with animations diff --git a/components/Animations/src/Xaml/ImplicitAnimationSet.cs b/components/Animations/src/Xaml/ImplicitAnimationSet.cs index 259ab72c..412a0771 100644 --- a/components/Animations/src/Xaml/ImplicitAnimationSet.cs +++ b/components/Animations/src/Xaml/ImplicitAnimationSet.cs @@ -36,7 +36,7 @@ namespace CommunityToolkit.WinUI.Animations; /// one to another, and doing so will add unnecessary runtime overhead over time. If you want to apply the same animations /// to multiple elements, simply create another instance and another set of animations with the same properties within it. /// -public sealed class ImplicitAnimationSet : DependencyObjectCollection +public sealed partial class ImplicitAnimationSet : DependencyObjectCollection { /// /// Raised whenever any configuration change occurrs within the current instance. diff --git a/components/Behaviors/samples/InvokeActionsSample.xaml b/components/Behaviors/samples/InvokeActionsSample.xaml index 47f1e748..9bad2da2 100644 --- a/components/Behaviors/samples/InvokeActionsSample.xaml +++ b/components/Behaviors/samples/InvokeActionsSample.xaml @@ -1,11 +1,10 @@ - + - + - - + + - + diff --git a/components/Behaviors/samples/KeyDownTriggerBehaviorSample.xaml b/components/Behaviors/samples/KeyDownTriggerBehaviorSample.xaml index 9594ef02..40a347c6 100644 --- a/components/Behaviors/samples/KeyDownTriggerBehaviorSample.xaml +++ b/components/Behaviors/samples/KeyDownTriggerBehaviorSample.xaml @@ -2,7 +2,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" - xmlns:core="using:Microsoft.Xaml.Interactions.Core" xmlns:interactivity="using:Microsoft.Xaml.Interactivity"> - + diff --git a/components/Behaviors/samples/NavigateToUriActionSample.xaml b/components/Behaviors/samples/NavigateToUriActionSample.xaml index 980a9d94..e6a3dd7c 100644 --- a/components/Behaviors/samples/NavigateToUriActionSample.xaml +++ b/components/Behaviors/samples/NavigateToUriActionSample.xaml @@ -2,14 +2,13 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" - xmlns:core="using:Microsoft.Xaml.Interactions.Core" xmlns:interactivity="using:Microsoft.Xaml.Interactivity"> diff --git a/components/Behaviors/samples/StartAnimationActivitySample.xaml b/components/Behaviors/samples/StartAnimationActivitySample.xaml index cc850756..a349727f 100644 --- a/components/Behaviors/samples/StartAnimationActivitySample.xaml +++ b/components/Behaviors/samples/StartAnimationActivitySample.xaml @@ -5,7 +5,6 @@ xmlns:ani="using:CommunityToolkit.WinUI.Animations" xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:local="using:BehaviorsExperiment.Samples" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" @@ -55,9 +54,9 @@ - + - + diff --git a/components/Behaviors/src/CommunityToolkit.WinUI.Behaviors.csproj b/components/Behaviors/src/CommunityToolkit.WinUI.Behaviors.csproj index 05f3c37b..402d7526 100644 --- a/components/Behaviors/src/CommunityToolkit.WinUI.Behaviors.csproj +++ b/components/Behaviors/src/CommunityToolkit.WinUI.Behaviors.csproj @@ -8,6 +8,7 @@ CommunityToolkit.WinUI.BehaviorsRns ReadMe.md + true diff --git a/components/Behaviors/src/Dependencies.props b/components/Behaviors/src/Dependencies.props index 3f06d427..0eb55f85 100644 --- a/components/Behaviors/src/Dependencies.props +++ b/components/Behaviors/src/Dependencies.props @@ -11,21 +11,21 @@ - + - + - + - + diff --git a/components/Behaviors/src/FocusTargetList.cs b/components/Behaviors/src/FocusTargetList.cs index 65ae0778..1ef261aa 100644 --- a/components/Behaviors/src/FocusTargetList.cs +++ b/components/Behaviors/src/FocusTargetList.cs @@ -7,6 +7,6 @@ namespace CommunityToolkit.WinUI.Behaviors; /// /// A collection of . /// -public sealed class FocusTargetList : List +public sealed partial class FocusTargetList : List { } diff --git a/components/CameraPreview/samples/CameraPreviewSample.xaml.cs b/components/CameraPreview/samples/CameraPreviewSample.xaml.cs index d2750aaa..36964192 100644 --- a/components/CameraPreview/samples/CameraPreviewSample.xaml.cs +++ b/components/CameraPreview/samples/CameraPreviewSample.xaml.cs @@ -92,7 +92,7 @@ protected async override void OnNavigatedFrom(NavigationEventArgs e) await CleanUpAsync(); } - private async void Application_Suspending(object sender, SuspendingEventArgs e) + private async void Application_Suspending(object? sender, SuspendingEventArgs e) { if (Frame?.CurrentSourcePageType == typeof(CameraPreviewSample)) { @@ -102,7 +102,7 @@ private async void Application_Suspending(object sender, SuspendingEventArgs e) } } - private async void Application_Resuming(object sender, object e) + private async void Application_Resuming(object? sender, object e) { if (CameraPreviewControl != null) { @@ -113,19 +113,19 @@ private async void Application_Resuming(object sender, object e) } } - private void CameraPreviewControl_FrameArrived(object sender, FrameEventArgs e) + private void CameraPreviewControl_FrameArrived(object? sender, FrameEventArgs e) { _currentVideoFrame = e.VideoFrame; } - private void CameraPreviewControl_PreviewFailed(object sender, PreviewFailedEventArgs e) + private void CameraPreviewControl_PreviewFailed(object? sender, PreviewFailedEventArgs e) { ErrorBar.Message = e.Error; ErrorBar.IsOpen = true; } - private async void CaptureButton_Click(object sender, RoutedEventArgs e) + private async void CaptureButton_Click(object? sender, RoutedEventArgs e) { var softwareBitmap = _currentVideoFrame?.SoftwareBitmap; diff --git a/components/Collections/samples/AdvancedCollectionView.md b/components/Collections/samples/AdvancedCollectionView.md index c2c37bd1..bab2750e 100644 --- a/components/Collections/samples/AdvancedCollectionView.md +++ b/components/Collections/samples/AdvancedCollectionView.md @@ -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 @@ -12,11 +12,9 @@ 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 @@ -24,58 +22,13 @@ In your viewmodel instead of having a public [IEnumerable](https://learn.microso * 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 -{ - 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 diff --git a/components/Collections/samples/AdvancedCollectionViewSample.xaml b/components/Collections/samples/AdvancedCollectionViewSample.xaml index 80801029..58349990 100644 --- a/components/Collections/samples/AdvancedCollectionViewSample.xaml +++ b/components/Collections/samples/AdvancedCollectionViewSample.xaml @@ -1,9 +1,9 @@ - + @@ -17,8 +17,9 @@ - - + + + ItemTemplate="{StaticResource EmployeeDataTemplate}" + ItemsSource="{x:Bind EmployeeCollection}" /> + ItemTemplate="{StaticResource EmployeeDataTemplate}" + ItemsSource="{x:Bind CollectionView}" /> diff --git a/components/Collections/samples/AdvancedCollectionViewSample.xaml.cs b/components/Collections/samples/AdvancedCollectionViewSample.xaml.cs index a7482a6d..8dfb75e6 100644 --- a/components/Collections/samples/AdvancedCollectionViewSample.xaml.cs +++ b/components/Collections/samples/AdvancedCollectionViewSample.xaml.cs @@ -3,13 +3,16 @@ // 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? oc; + public ObservableCollection EmployeeCollection { get; private set; } + + public AdvancedCollectionView CollectionView { get; private set; } public AdvancedCollectionViewSample() { @@ -17,57 +20,56 @@ public AdvancedCollectionViewSample() Setup(); } + [MemberNotNull(nameof(EmployeeCollection))] + [MemberNotNull(nameof(CollectionView))] private void Setup() { // left list - oc = new ObservableCollection + 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 = ""; } } +} +/// +/// A sample class used to show how to use the class. +/// +public partial class Employee +{ /// - /// A sample class used to show how to use the interface. + /// Gets or sets the name of the person. /// - public class Person - { - /// - /// Gets or sets the name of the person. - /// - public string? Name { get; set; } - } + public string? Name { get; set; } } diff --git a/components/Collections/samples/Collections.Samples.csproj b/components/Collections/samples/Collections.Samples.csproj index 1aa967d9..3439c3f9 100644 --- a/components/Collections/samples/Collections.Samples.csproj +++ b/components/Collections/samples/Collections.Samples.csproj @@ -23,4 +23,10 @@ PreserveNewest + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/components/Collections/samples/IncrementalLoadingCollectionSample.xaml b/components/Collections/samples/IncrementalLoadingCollectionSample.xaml index 2557ed51..19ab8f69 100644 --- a/components/Collections/samples/IncrementalLoadingCollectionSample.xaml +++ b/components/Collections/samples/IncrementalLoadingCollectionSample.xaml @@ -1,9 +1,9 @@ - + @@ -19,18 +19,18 @@ /// -public class PeopleSource : IIncrementalSource +public partial class PeopleSource : IIncrementalSource { private readonly List _people; @@ -94,7 +81,7 @@ public PeopleSource() /// /// A sample class used to show how to use the interface. /// -public class Person +public partial class Person { /// /// Gets or sets the name of the person. diff --git a/components/Collections/src/AdvancedCollectionView/AdvancedCollectionView.Defer.cs b/components/Collections/src/AdvancedCollectionView/AdvancedCollectionView.Defer.cs index 42f0bbcb..92ef1355 100644 --- a/components/Collections/src/AdvancedCollectionView/AdvancedCollectionView.Defer.cs +++ b/components/Collections/src/AdvancedCollectionView/AdvancedCollectionView.Defer.cs @@ -22,7 +22,7 @@ public IDisposable DeferRefresh() /// Notification deferrer helper class /// #pragma warning disable CA1063 // Implement IDisposable Correctly - public class NotificationDeferrer : IDisposable + public partial class NotificationDeferrer : IDisposable #pragma warning restore CA1063 // Implement IDisposable Correctly { private readonly AdvancedCollectionView _acvs; diff --git a/components/Collections/src/AdvancedCollectionView/VectorChangedEventArgs.cs b/components/Collections/src/AdvancedCollectionView/VectorChangedEventArgs.cs index 0e5be060..0b6733aa 100644 --- a/components/Collections/src/AdvancedCollectionView/VectorChangedEventArgs.cs +++ b/components/Collections/src/AdvancedCollectionView/VectorChangedEventArgs.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Collections; /// /// Vector changed EventArgs /// -internal class VectorChangedEventArgs : IVectorChangedEventArgs +internal partial class VectorChangedEventArgs : IVectorChangedEventArgs { /// /// Initializes a new instance of the class. diff --git a/components/Collections/src/CommunityToolkit.WinUI.Collections.csproj b/components/Collections/src/CommunityToolkit.WinUI.Collections.csproj index 68138678..1e278d17 100644 --- a/components/Collections/src/CommunityToolkit.WinUI.Collections.csproj +++ b/components/Collections/src/CommunityToolkit.WinUI.Collections.csproj @@ -8,6 +8,7 @@ CommunityToolkit.WinUI.CollectionsRns ReadMe.md + true diff --git a/components/ColorPicker/src/ColorPicker.cs b/components/ColorPicker/src/ColorPicker.cs index eb5e0690..a9f0677c 100644 --- a/components/ColorPicker/src/ColorPicker.cs +++ b/components/ColorPicker/src/ColorPicker.cs @@ -358,7 +358,7 @@ private void UpdateVisualState(bool useTransitions = true) VisualStateManager.GoToState(this, (Truth(IsColorPaletteVisible, IsColorSpectrumVisible, IsColorChannelTextInputVisible) <= 1) ? "ColorPanelSelectorCollapsed" : "ColorPanelSelectorVisible", useTransitions); } - public static int Truth(params bool[] booleans) + private static int Truth(params bool[] booleans) { return booleans.Count(b => b); } diff --git a/components/ColorPicker/src/ColorPicker.xaml b/components/ColorPicker/src/ColorPicker.xaml index 284eb7b2..557f6ab0 100644 --- a/components/ColorPicker/src/ColorPicker.xaml +++ b/components/ColorPicker/src/ColorPicker.xaml @@ -1,4 +1,4 @@ - + @@ -291,7 +292,7 @@ animations:Implicit.HideAnimations="{StaticResource HideTransitions}" animations:Implicit.ShowAnimations="{StaticResource ShowTransitions}" ItemsSource="{TemplateBinding CustomPaletteColors}" - SelectedValue="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Color, Mode=TwoWay}" + SelectedValue="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Color, Converter={StaticResource NullToTransparentConverter}, Mode=TwoWay}" SelectionMode="Single" Tag="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Color, Mode=OneWay}"> diff --git a/components/ColorPicker/src/CommunityToolkit.WinUI.Controls.ColorPicker.csproj b/components/ColorPicker/src/CommunityToolkit.WinUI.Controls.ColorPicker.csproj index 87f7d0e3..b5a8e663 100644 --- a/components/ColorPicker/src/CommunityToolkit.WinUI.Controls.ColorPicker.csproj +++ b/components/ColorPicker/src/CommunityToolkit.WinUI.Controls.ColorPicker.csproj @@ -6,6 +6,7 @@ CommunityToolkit.WinUI.Controls.ColorPickerRns + true @@ -29,4 +30,15 @@ <_LayoutFile Remove="@(_LayoutFile)" Condition="$([System.String]::Copy("%(_LayoutFile.TargetPath)").StartsWith('CommunityToolkit.WinUI.Controls.Segmented\'))" /> + + + + $(NoWarn)IL2059; + diff --git a/components/ColorPicker/src/Converters/AccentColorConverter.cs b/components/ColorPicker/src/Converters/AccentColorConverter.cs index 502cfc71..2c3204fd 100644 --- a/components/ColorPicker/src/Converters/AccentColorConverter.cs +++ b/components/ColorPicker/src/Converters/AccentColorConverter.cs @@ -11,7 +11,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// Creates an accent color for a base color value. /// -public class AccentColorConverter : IValueConverter +public partial class AccentColorConverter : IValueConverter { /// /// The amount to change the Value channel for each accent color step. diff --git a/components/ColorPicker/src/Converters/ColorToHexConverter.cs b/components/ColorPicker/src/Converters/ColorToHexConverter.cs index db6eec76..5eadf247 100644 --- a/components/ColorPicker/src/Converters/ColorToHexConverter.cs +++ b/components/ColorPicker/src/Converters/ColorToHexConverter.cs @@ -10,7 +10,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// Converts a color to a hex string and vice versa. /// -public class ColorToHexConverter : IValueConverter +public partial class ColorToHexConverter : IValueConverter { /// public object Convert( diff --git a/components/ColorPicker/src/Converters/ContrastBrushConverter.cs b/components/ColorPicker/src/Converters/ContrastBrushConverter.cs index faf4a117..6b73c88f 100644 --- a/components/ColorPicker/src/Converters/ContrastBrushConverter.cs +++ b/components/ColorPicker/src/Converters/ContrastBrushConverter.cs @@ -15,7 +15,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// Gets a color, either black or white, depending on the brightness of the supplied color. /// -public class ContrastBrushConverter : IValueConverter +public partial class ContrastBrushConverter : IValueConverter { /// /// Gets or sets the alpha channel threshold below which a default color is used instead of black/white. diff --git a/components/ColorPicker/src/Converters/NullToTransparentConverter.cs b/components/ColorPicker/src/Converters/NullToTransparentConverter.cs new file mode 100644 index 00000000..1d33c538 --- /dev/null +++ b/components/ColorPicker/src/Converters/NullToTransparentConverter.cs @@ -0,0 +1,22 @@ +// 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; + +/// +/// Value converter that converts null values to Transparent. +/// +public partial class NullToTransparentConverter : IValueConverter +{ + /// + public object Convert(object value, Type targetType, object parameter, string language) => value; + + /// + public object ConvertBack(object? value, Type targetType, object parameter, string language) => value ?? +#if WINUI2 + Windows.UI.Colors.Transparent; +#else + Microsoft.UI.Colors.Transparent; +#endif +} diff --git a/components/Converters/src/BoolNegationConverter.cs b/components/Converters/src/BoolNegationConverter.cs index 3eeb1fd5..164d8cbf 100644 --- a/components/Converters/src/BoolNegationConverter.cs +++ b/components/Converters/src/BoolNegationConverter.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Converters; /// /// Value converter that applies NOT operator to a value. /// -public class BoolNegationConverter : IValueConverter +public partial class BoolNegationConverter : IValueConverter { /// /// Convert a boolean value to its negation. diff --git a/components/Converters/src/BoolToVisibilityConverter.cs b/components/Converters/src/BoolToVisibilityConverter.cs index deb1fa65..25c19fd7 100644 --- a/components/Converters/src/BoolToVisibilityConverter.cs +++ b/components/Converters/src/BoolToVisibilityConverter.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Converters; /// /// This class converts a boolean value into a Visibility enumeration. /// -public class BoolToVisibilityConverter : BoolToObjectConverter +public partial class BoolToVisibilityConverter : BoolToObjectConverter { /// /// Initializes a new instance of the class. diff --git a/components/Converters/src/CollectionVisibilityConverter.cs b/components/Converters/src/CollectionVisibilityConverter.cs index a626ebc7..c322d396 100644 --- a/components/Converters/src/CollectionVisibilityConverter.cs +++ b/components/Converters/src/CollectionVisibilityConverter.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Converters; /// /// This class converts a collection size to visibility. /// -public class CollectionVisibilityConverter : EmptyCollectionToObjectConverter +public partial class CollectionVisibilityConverter : EmptyCollectionToObjectConverter { /// /// Initializes a new instance of the class. diff --git a/components/Converters/src/ColorToDisplayNameConverter.cs b/components/Converters/src/ColorToDisplayNameConverter.cs index bc5a83f2..5cdaf36f 100644 --- a/components/Converters/src/ColorToDisplayNameConverter.cs +++ b/components/Converters/src/ColorToDisplayNameConverter.cs @@ -9,7 +9,7 @@ namespace CommunityToolkit.WinUI.Converters; /// /// Gets the approximated display name for the color. /// -public class ColorToDisplayNameConverter : IValueConverter +public partial class ColorToDisplayNameConverter : IValueConverter { /// public object Convert( @@ -34,11 +34,14 @@ public object Convert( return DependencyProperty.UnsetValue; } -#if !WINAPPSDK && !HAS_UNO +#if WINDOWS_UWP && NET8_0_OR_GREATER + // Windows.UI.ColorHelper not yet supported on modern uwp. + // Following advice from Sergio0694 + return color.ToString(); +#elif WINUI2 return Windows.UI.ColorHelper.ToDisplayName(color); -#else - // ToDisplayName not yet supported on WASDK. See https://github.com/microsoft/microsoft-ui-xaml/issues/8287 - return "Not supported"; +#elif WINUI3 + return Microsoft.UI.ColorHelper.ToDisplayName(color); #endif } diff --git a/components/Converters/src/DoubleToVisibilityConverter.cs b/components/Converters/src/DoubleToVisibilityConverter.cs index 7e1bc731..48aad016 100644 --- a/components/Converters/src/DoubleToVisibilityConverter.cs +++ b/components/Converters/src/DoubleToVisibilityConverter.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Converters; /// /// This class converts a double value into a Visibility enumeration. /// -public class DoubleToVisibilityConverter : DoubleToObjectConverter +public partial class DoubleToVisibilityConverter : DoubleToObjectConverter { /// /// Initializes a new instance of the class. diff --git a/components/Converters/src/EmptyCollectionToObjectConverter.cs b/components/Converters/src/EmptyCollectionToObjectConverter.cs index 908e468d..5ddefe6b 100644 --- a/components/Converters/src/EmptyCollectionToObjectConverter.cs +++ b/components/Converters/src/EmptyCollectionToObjectConverter.cs @@ -10,7 +10,7 @@ namespace CommunityToolkit.WinUI.Converters; /// This class converts a collection size into an other object. /// Can be used to convert to bind a visibility, a color or an image to the size of the collection. /// -public class EmptyCollectionToObjectConverter : EmptyObjectToObjectConverter +public partial class EmptyCollectionToObjectConverter : EmptyObjectToObjectConverter { /// /// Checks collection for emptiness. diff --git a/components/Converters/src/EmptyStringToObjectConverter.cs b/components/Converters/src/EmptyStringToObjectConverter.cs index 32ef44bd..c01d65d4 100644 --- a/components/Converters/src/EmptyStringToObjectConverter.cs +++ b/components/Converters/src/EmptyStringToObjectConverter.cs @@ -8,7 +8,7 @@ namespace CommunityToolkit.WinUI.Converters; /// This class converts a string value into a an object (if the value is null or empty returns the false value). /// Can be used to bind a visibility, a color or an image to the value of a string. /// -public class EmptyStringToObjectConverter : EmptyObjectToObjectConverter +public partial class EmptyStringToObjectConverter : EmptyObjectToObjectConverter { /// /// Checks string for emptiness. diff --git a/components/Converters/src/FileSizeToFriendlyStringConverter.cs b/components/Converters/src/FileSizeToFriendlyStringConverter.cs index 93db3317..c5d4e3b2 100644 --- a/components/Converters/src/FileSizeToFriendlyStringConverter.cs +++ b/components/Converters/src/FileSizeToFriendlyStringConverter.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Converters; /// /// Converts a file size in bytes to a more human-readable friendly format using /// -public class FileSizeToFriendlyStringConverter : IValueConverter +public partial class FileSizeToFriendlyStringConverter : IValueConverter { /// public object Convert(object value, Type targetType, object parameter, string language) diff --git a/components/Converters/src/IFormattableToStringConverter.cs b/components/Converters/src/IFormattableToStringConverter.cs index e95f8a98..b099e2c3 100644 --- a/components/Converters/src/IFormattableToStringConverter.cs +++ b/components/Converters/src/IFormattableToStringConverter.cs @@ -8,7 +8,7 @@ namespace CommunityToolkit.WinUI.Converters; /// Value converter that converts an to a formatted . /// The string format needs to be passed as the converter parameter. /// -public class IFormattableToStringConverter : IValueConverter +public partial class IFormattableToStringConverter : IValueConverter { // TODO: Provide property to set a IFormatProvider for the 2nd parameter to ToString diff --git a/components/Converters/src/ResourceNameToResourceStringConverter.cs b/components/Converters/src/ResourceNameToResourceStringConverter.cs index fd498650..8be8182e 100644 --- a/components/Converters/src/ResourceNameToResourceStringConverter.cs +++ b/components/Converters/src/ResourceNameToResourceStringConverter.cs @@ -15,7 +15,7 @@ namespace CommunityToolkit.WinUI.Converters; /// /// Value converter that look up for the source string in the App Resources strings and returns its value, if found. /// -public sealed class ResourceNameToResourceStringConverter : IValueConverter +public sealed partial class ResourceNameToResourceStringConverter : IValueConverter { #if WINAPPSDK && !HAS_UNO private readonly ResourceManager _resourceManager = new ResourceManager(); @@ -32,17 +32,18 @@ public sealed class ResourceNameToResourceStringConverter : IValueConverter /// Optional parameter. Not used. /// The language of the conversion. /// The string corresponding to the resource name. - public object Convert(object value, Type targetType, object parameter, string language) + public object? Convert(object value, Type targetType, object parameter, string language) { - if (value == null) + var stringValue = value?.ToString(); + if (stringValue is null) { return string.Empty; } #if WINAPPSDK && !HAS_UNO - return _resourceManager.MainResourceMap.TryGetValue(value.ToString()).ValueAsString; + return _resourceManager.MainResourceMap.TryGetValue(stringValue).ValueAsString; #else - return _resourceLoader.GetString(value.ToString()); + return _resourceLoader.GetString(stringValue); #endif } diff --git a/components/Converters/src/StringFormatConverter.cs b/components/Converters/src/StringFormatConverter.cs index 4ea75524..e0b10d8f 100644 --- a/components/Converters/src/StringFormatConverter.cs +++ b/components/Converters/src/StringFormatConverter.cs @@ -9,7 +9,7 @@ namespace CommunityToolkit.WinUI.Converters; /// /// This class provides a binding converter to display formatted strings /// -public class StringFormatConverter : IValueConverter +public partial class StringFormatConverter : IValueConverter { /// /// Return the formatted string version of the source object. diff --git a/components/Converters/src/StringVisibilityConverter.cs b/components/Converters/src/StringVisibilityConverter.cs index 1291edde..edbb669f 100644 --- a/components/Converters/src/StringVisibilityConverter.cs +++ b/components/Converters/src/StringVisibilityConverter.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Converters; /// /// This class converts a string value into a Visibility value (if the value is null or empty returns a collapsed value). /// -public class StringVisibilityConverter : EmptyStringToObjectConverter +public partial class StringVisibilityConverter : EmptyStringToObjectConverter { /// /// Initializes a new instance of the class. diff --git a/components/Converters/src/TaskResultConverter.cs b/components/Converters/src/TaskResultConverter.cs index 72bc54a7..3c2e84cc 100644 --- a/components/Converters/src/TaskResultConverter.cs +++ b/components/Converters/src/TaskResultConverter.cs @@ -17,7 +17,7 @@ namespace CommunityToolkit.WinUI.Converters; #if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to try to access the Task.Result property of the input Task instance.")] #endif -public sealed class TaskResultConverter : IValueConverter +public sealed partial class TaskResultConverter : IValueConverter { /// public object Convert(object value, Type targetType, object parameter, string language) diff --git a/components/Converters/src/VisibilityToBoolConverter.cs b/components/Converters/src/VisibilityToBoolConverter.cs index 6813c78b..5bf25c00 100644 --- a/components/Converters/src/VisibilityToBoolConverter.cs +++ b/components/Converters/src/VisibilityToBoolConverter.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Converters; /// /// This class converts a Visibility enumeration to a boolean value. /// -public class VisibilityToBoolConverter : IValueConverter +public partial class VisibilityToBoolConverter : IValueConverter { /// /// Convert a value to boolean. diff --git a/components/Extensions/samples/Dispatcher/KeyboardDebounceSample.xaml b/components/Extensions/samples/Dispatcher/KeyboardDebounceSample.xaml new file mode 100644 index 00000000..3de79e5e --- /dev/null +++ b/components/Extensions/samples/Dispatcher/KeyboardDebounceSample.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/components/Extensions/samples/Dispatcher/KeyboardDebounceSample.xaml.cs b/components/Extensions/samples/Dispatcher/KeyboardDebounceSample.xaml.cs new file mode 100644 index 00000000..e3ce6ee2 --- /dev/null +++ b/components/Extensions/samples/Dispatcher/KeyboardDebounceSample.xaml.cs @@ -0,0 +1,42 @@ +// 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; +#if WINAPPSDK +using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue; +using DispatcherQueueTimer = Microsoft.UI.Dispatching.DispatcherQueueTimer; +#else +using DispatcherQueue = Windows.System.DispatcherQueue; +using DispatcherQueueTimer = Windows.System.DispatcherQueueTimer; +#endif + +namespace ExtensionsExperiment.Samples.DispatcherQueueExtensions; + +[ToolkitSample(id: nameof(KeyboardDebounceSample), "DispatcherQueueTimer Debounce Keyboard", description: "A sample for showing how to use the DispatcherQueueTimer Debounce extension to smooth keyboard input.")] +[ToolkitSampleNumericOption("Interval", 120, 60, 240)] +public sealed partial class KeyboardDebounceSample : Page +{ + public DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); + + public KeyboardDebounceSample() + { + InitializeComponent(); + } + + private void TextBox_TextChanged(object sender, TextChangedEventArgs e) + { + if (sender is TextBox textBox) + { + _debounceTimer.Debounce(() => + { + ResultText.Text = textBox.Text; + }, + //// i.e. if another keyboard press comes in within 120ms of the last, we'll wait before we fire off the request + interval: TimeSpan.FromMilliseconds(Interval), + //// If we're blanking out or the first character type, we'll start filtering immediately instead to appear more responsive. + //// We want to switch back to trailing as the user types more so that we still capture all the input. + immediate: textBox.Text.Length <= 1); + } + } +} diff --git a/components/Extensions/samples/Dispatcher/MouseDebounceSample.xaml b/components/Extensions/samples/Dispatcher/MouseDebounceSample.xaml new file mode 100644 index 00000000..d44d17f7 --- /dev/null +++ b/components/Extensions/samples/Dispatcher/MouseDebounceSample.xaml @@ -0,0 +1,14 @@ + + + + public static class DispatcherQueueTimerExtensions { - private static ConcurrentDictionary _debounceInstances = new ConcurrentDictionary(); + /// + private static ConditionalWeakTable _debounceInstances = new(); /// - /// Used to debounce (rate-limit) an event. The action will be postponed and executed after the interval has elapsed. At the end of the interval, the function will be called with the arguments that were passed most recently to the debounced function. + /// Used to debounce (rate-limit) an event. The action will be postponed and executed after the interval has elapsed. At the end of the interval, the function will be called with the arguments that were passed most recently to the debounced function. Useful for smoothing keyboard input, for instance. /// Use this method to control the timer instead of calling Start/Interval/Stop manually. /// A scheduled debounce can still be stopped by calling the stop method on the timer instance. /// Each timer can only have one debounced function limited at a time. @@ -28,14 +31,14 @@ public static class DispatcherQueueTimerExtensions /// Timer instance, only one debounced function can be used per timer. /// Action to execute at the end of the interval. /// Interval to wait before executing the action. - /// Determines if the action execute on the leading edge instead of trailing edge. + /// Determines if the action execute on the leading edge instead of trailing edge of the interval. Subsequent input will be ignored into the interval has completed. Useful for ignore extraneous extra input like multiple mouse clicks. /// /// /// private DispatcherQueueTimer _typeTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); /// /// _typeTimer.Debounce(async () => /// { - /// // Only executes this code after 0.3 seconds have elapsed since last trigger. + /// // Only executes code put here after 0.3 seconds have elapsed since last call to Debounce. /// }, TimeSpan.FromSeconds(0.3)); /// /// @@ -52,8 +55,20 @@ public static void Debounce(this DispatcherQueueTimer timer, Action action, Time timer.Tick -= Timer_Tick; timer.Interval = interval; + // Ensure we haven't been misconfigured and won't execute more times than we expect. + timer.IsRepeating = false; + if (immediate) { + // If we have a _debounceInstance queued, then we were running in trailing mode, + // so if we now have the immediate flag, we should ignore this timer, and run immediately. + if (_debounceInstances.TryGetValue(timer, out var _)) + { + timeout = false; + + _debounceInstances.Remove(timer); + } + // If we're in immediate mode then we only execute if the timer wasn't running beforehand if (!timeout) { @@ -66,7 +81,7 @@ public static void Debounce(this DispatcherQueueTimer timer, Action action, Time timer.Tick += Timer_Tick; // Store/Update function - _debounceInstances.AddOrUpdate(timer, action, (k, v) => action); + _debounceInstances.AddOrUpdate(timer, action); } // Start the timer to keep track of the last call here. @@ -81,8 +96,9 @@ private static void Timer_Tick(object sender, object e) timer.Tick -= Timer_Tick; timer.Stop(); - if (_debounceInstances.TryRemove(timer, out Action? action)) + if (_debounceInstances.TryGetValue(timer, out Action? action)) { + _debounceInstances.Remove(timer); action?.Invoke(); } } diff --git a/components/Extensions/src/ListViewBase/ListViewExtensions.AlternateRows.cs b/components/Extensions/src/ListViewBase/ListViewExtensions.AlternateRows.cs index 65ef5340..1e73c325 100644 --- a/components/Extensions/src/ListViewBase/ListViewExtensions.AlternateRows.cs +++ b/components/Extensions/src/ListViewBase/ListViewExtensions.AlternateRows.cs @@ -192,10 +192,20 @@ private static void SetItemContainerBackground(ListViewBase sender, Control item if (itemIndex % 2 == 0) { itemContainer.Background = GetAlternateColor(sender); + var rootBorder = itemContainer.FindDescendant(); + if (rootBorder != null) + { + rootBorder.Background = GetAlternateColor(sender); + } } else { itemContainer.Background = null; + var rootBorder = itemContainer.FindDescendant(); + if (rootBorder != null) + { + rootBorder.Background = null; + } } } } diff --git a/components/Extensions/src/Markup/Abstract/TextIconExtension.cs b/components/Extensions/src/Markup/Abstract/TextIconExtension.cs index 2354661a..c603cb3e 100644 --- a/components/Extensions/src/Markup/Abstract/TextIconExtension.cs +++ b/components/Extensions/src/Markup/Abstract/TextIconExtension.cs @@ -4,8 +4,9 @@ #if WINAPPSDK using Microsoft.UI.Text; -#endif +#else using Windows.UI.Text; +#endif namespace CommunityToolkit.WinUI; @@ -33,12 +34,20 @@ protected static FontFamily SymbolThemeFontFamily /// /// Gets or sets the thickness of the icon glyph. /// - public FontWeight FontWeight { get; set; } = FontWeights.Normal; + #if WINUI3 + public Windows.UI.Text.FontWeight FontWeight { get; set; } = Microsoft.UI.Text.FontWeights.Normal; + #elif WINUI2 + public Windows.UI.Text.FontWeight FontWeight { get; set; } = Windows.UI.Text.FontWeights.Normal; + #endif /// /// Gets or sets the font style for the icon glyph. /// - public FontStyle FontStyle { get; set; } = FontStyle.Normal; + #if WINUI3 + public Windows.UI.Text.FontStyle FontStyle { get; set; } = Windows.UI.Text.FontStyle.Normal; + #elif WINUI2 + public Windows.UI.Text.FontStyle FontStyle { get; set; } = Windows.UI.Text.FontStyle.Normal; + #endif /// /// Gets or sets the foreground for the icon. diff --git a/components/Extensions/src/Markup/BitmapIconExtension.cs b/components/Extensions/src/Markup/BitmapIconExtension.cs index b48f81ff..68140d1d 100644 --- a/components/Extensions/src/Markup/BitmapIconExtension.cs +++ b/components/Extensions/src/Markup/BitmapIconExtension.cs @@ -8,7 +8,7 @@ namespace CommunityToolkit.WinUI; /// Custom which can provide values. /// [MarkupExtensionReturnType(ReturnType = typeof(BitmapIcon))] -public sealed class BitmapIconExtension : MarkupExtension +public sealed partial class BitmapIconExtension : MarkupExtension { /// /// Gets or sets the representing the image to display. diff --git a/components/Extensions/src/Markup/BitmapIconSourceExtension.cs b/components/Extensions/src/Markup/BitmapIconSourceExtension.cs index 85a70994..26136aa9 100644 --- a/components/Extensions/src/Markup/BitmapIconSourceExtension.cs +++ b/components/Extensions/src/Markup/BitmapIconSourceExtension.cs @@ -8,7 +8,7 @@ namespace CommunityToolkit.WinUI; /// Custom which can provide values. /// [MarkupExtensionReturnType(ReturnType = typeof(BitmapIconSource))] -public sealed class BitmapIconSourceExtension : MarkupExtension +public sealed partial class BitmapIconSourceExtension : MarkupExtension { /// /// Gets or sets the representing the image to display. diff --git a/components/Extensions/src/Markup/EnumValuesExtension.cs b/components/Extensions/src/Markup/EnumValuesExtension.cs index d0a1751e..833b7069 100644 --- a/components/Extensions/src/Markup/EnumValuesExtension.cs +++ b/components/Extensions/src/Markup/EnumValuesExtension.cs @@ -11,7 +11,7 @@ namespace CommunityToolkit.WinUI; #if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("It might not be possible to create an array of a user-defined enum type at runtime.")] #endif -public sealed class EnumValuesExtension : MarkupExtension +public sealed partial class EnumValuesExtension : MarkupExtension { /// /// Gets or sets the of the target diff --git a/components/Extensions/src/Markup/FontIconExtension.cs b/components/Extensions/src/Markup/FontIconExtension.cs index 4a090833..98704666 100644 --- a/components/Extensions/src/Markup/FontIconExtension.cs +++ b/components/Extensions/src/Markup/FontIconExtension.cs @@ -8,7 +8,7 @@ namespace CommunityToolkit.WinUI; /// Custom which can provide values. /// [MarkupExtensionReturnType(ReturnType = typeof(FontIcon))] -public class FontIconExtension : TextIconExtension +public partial class FontIconExtension : TextIconExtension { /// /// Gets or sets the value representing the icon to display. diff --git a/components/Extensions/src/Markup/FontIconSourceExtension.cs b/components/Extensions/src/Markup/FontIconSourceExtension.cs index f09a9bf9..617e9710 100644 --- a/components/Extensions/src/Markup/FontIconSourceExtension.cs +++ b/components/Extensions/src/Markup/FontIconSourceExtension.cs @@ -8,7 +8,7 @@ namespace CommunityToolkit.WinUI; /// Custom which can provide values. /// [MarkupExtensionReturnType(ReturnType = typeof(FontIconSource))] -public class FontIconSourceExtension : TextIconExtension +public partial class FontIconSourceExtension : TextIconExtension { /// /// Gets or sets the value representing the icon to display. diff --git a/components/Extensions/src/Markup/NullableBoolExtension.cs b/components/Extensions/src/Markup/NullableBoolExtension.cs index c55cb607..de7b1a00 100644 --- a/components/Extensions/src/Markup/NullableBoolExtension.cs +++ b/components/Extensions/src/Markup/NullableBoolExtension.cs @@ -9,7 +9,7 @@ namespace CommunityToolkit.WinUI; /// See https://wpdev.uservoice.com/forums/110705-universal-windows-platform/suggestions/17767198-nullable-dependency-properties. /// [MarkupExtensionReturnType(ReturnType = typeof(bool?))] -public class NullableBoolExtension : MarkupExtension +public partial class NullableBoolExtension : MarkupExtension { /// /// Gets or sets a value indicating whether the value of the Boolean is true. Ignored if is true. diff --git a/components/Extensions/src/Markup/OnDeviceExtension.cs b/components/Extensions/src/Markup/OnDeviceExtension.cs index 42335ee0..3f388b24 100644 --- a/components/Extensions/src/Markup/OnDeviceExtension.cs +++ b/components/Extensions/src/Markup/OnDeviceExtension.cs @@ -20,7 +20,7 @@ namespace CommunityToolkit.WinUI; /// xmlns:ui="using:CommunityToolkit.WinUI.UI" /> /// /// -public class OnDeviceExtension : MarkupExtension +public partial class OnDeviceExtension : MarkupExtension { /// /// Gets the current device family. diff --git a/components/Extensions/src/Markup/SymbolIconExtension.cs b/components/Extensions/src/Markup/SymbolIconExtension.cs index 3a33e533..b2af2344 100644 --- a/components/Extensions/src/Markup/SymbolIconExtension.cs +++ b/components/Extensions/src/Markup/SymbolIconExtension.cs @@ -8,7 +8,7 @@ namespace CommunityToolkit.WinUI; /// Custom which can provide symbol-based values. /// [MarkupExtensionReturnType(ReturnType = typeof(FontIcon))] -public class SymbolIconExtension : TextIconExtension +public partial class SymbolIconExtension : TextIconExtension { /// /// Gets or sets the value representing the icon to display. diff --git a/components/Extensions/src/Markup/SymbolIconSourceExtension.cs b/components/Extensions/src/Markup/SymbolIconSourceExtension.cs index 555db096..e3731817 100644 --- a/components/Extensions/src/Markup/SymbolIconSourceExtension.cs +++ b/components/Extensions/src/Markup/SymbolIconSourceExtension.cs @@ -8,7 +8,7 @@ namespace CommunityToolkit.WinUI; /// Custom which can provide symbol-baased values. /// [MarkupExtensionReturnType(ReturnType = typeof(FontIconSource))] -public class SymbolIconSourceExtension : TextIconExtension +public partial class SymbolIconSourceExtension : TextIconExtension { /// /// Gets or sets the value representing the icon to display. diff --git a/components/Extensions/src/Shadows/AttachedShadowBase.cs b/components/Extensions/src/Shadows/AttachedShadowBase.cs index 90145fe1..add9ac80 100644 --- a/components/Extensions/src/Shadows/AttachedShadowBase.cs +++ b/components/Extensions/src/Shadows/AttachedShadowBase.cs @@ -9,10 +9,12 @@ using Microsoft.UI; using Microsoft.UI.Composition; using Microsoft.UI.Xaml.Hosting; +using Colors = Microsoft.UI.Colors; #else using Windows.Foundation.Metadata; using Windows.UI.Composition; using Windows.UI.Xaml.Hosting; +using Colors = Windows.UI.Colors; #endif namespace CommunityToolkit.WinUI; diff --git a/components/Extensions/src/Text/StringExtensions.Localization.cs b/components/Extensions/src/Text/StringExtensions.Localization.cs index 080f238d..6f784125 100644 --- a/components/Extensions/src/Text/StringExtensions.Localization.cs +++ b/components/Extensions/src/Text/StringExtensions.Localization.cs @@ -49,7 +49,7 @@ static StringExtensions() /// You can retrieve this from a UIElement.UIContext, XamlRoot.UIContext (XamlIslands), or Window.UIContext. /// string value for given resource or empty string if not found. ////[SupportedOSPlatform("Windows10.0.18362.0")] - public static string GetViewLocalized(this string resourceKey, UIContext? uiContext = null) + public static string? GetViewLocalized(this string resourceKey, UIContext? uiContext = null) { if (uiContext != null) { diff --git a/components/Extensions/src/Tree/FrameworkElementExtensions.LogicalTree.cs b/components/Extensions/src/Tree/FrameworkElementExtensions.LogicalTree.cs index 52d27723..2ffaba73 100644 --- a/components/Extensions/src/Tree/FrameworkElementExtensions.LogicalTree.cs +++ b/components/Extensions/src/Tree/FrameworkElementExtensions.LogicalTree.cs @@ -662,6 +662,9 @@ public static IEnumerable FindParents(this FrameworkElement el /// /// The parent element. /// The retrieved content control, or if not available. + #if NET8_0_OR_GREATER + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "This method is currently not safe for trimming, and annotations here wouldn't help.")] + #endif public static UIElement? GetContentControl(this FrameworkElement element) { Type type = element.GetType(); diff --git a/components/Extensions/tests/DispatcherQueueTimerExtensionTests.cs b/components/Extensions/tests/DispatcherQueueTimerExtensionTests.cs index 036513a3..dddd9a4a 100644 --- a/components/Extensions/tests/DispatcherQueueTimerExtensionTests.cs +++ b/components/Extensions/tests/DispatcherQueueTimerExtensionTests.cs @@ -4,7 +4,6 @@ using CommunityToolkit.Tests; using CommunityToolkit.Tooling.TestGen; -using CommunityToolkit.WinUI; #if WINAPPSDK using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue; @@ -24,10 +23,17 @@ public partial class DispatcherQueueTimerExtensionTests : VisualUITestBase { [TestCategory("DispatcherQueueTimerExtensions")] [UIThreadTestMethod] - public async Task DispatcherQueueTimer_Debounce_Interrupt() + public async Task DispatcherQueueTimer_Debounce_Trailing() { var debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); + // Test custom event handler too + var customTriggeredCount = 0; + debounceTimer.Tick += (s, o) => + { + customTriggeredCount++; + }; + var triggeredCount = 0; string? triggeredValue = null; @@ -42,6 +48,94 @@ public async Task DispatcherQueueTimer_Debounce_Interrupt() Assert.AreEqual(true, debounceTimer.IsRunning, "Expected time to be running."); Assert.AreEqual(0, triggeredCount, "Function shouldn't have run yet."); + Assert.AreEqual(0, customTriggeredCount, "Custom Function shouldn't have run yet."); + Assert.IsNull(triggeredValue, "Function shouldn't have run yet."); + + await Task.Delay(TimeSpan.FromMilliseconds(80)); + + Assert.AreEqual(false, debounceTimer.IsRunning, "Expected to stop the timer."); + Assert.AreEqual(value, triggeredValue, "Expected result to be set."); + Assert.AreEqual(1, triggeredCount, "Expected to run once."); + Assert.AreEqual(1, customTriggeredCount, "Custom Function should have run once."); + } + + [TestCategory("DispatcherQueueTimerExtensions")] + [UIThreadTestMethod] + public async Task DispatcherQueueTimer_Debounce_Trailing_Stop() + { + var debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); + + // Test custom event handler too + var customTriggeredCount = 0; + debounceTimer.Tick += (s, o) => + { + customTriggeredCount++; + }; + + var triggeredCount = 0; + string? triggeredValue = null; + + var value = "He"; + debounceTimer.Debounce( + () => + { + triggeredCount++; + triggeredValue = value; + }, + TimeSpan.FromMilliseconds(60)); + + Assert.AreEqual(true, debounceTimer.IsRunning, "Expected timer to be running."); + Assert.AreEqual(0, triggeredCount, "Function shouldn't have run yet."); + Assert.AreEqual(0, customTriggeredCount, "Custom Function shouldn't have run yet."); + Assert.IsNull(triggeredValue, "Function shouldn't have run yet."); + + await Task.Delay(TimeSpan.FromMilliseconds(20)); + + // Stop the timer before it would fire. + debounceTimer.Stop(); + + Assert.AreEqual(false, debounceTimer.IsRunning, "Expected to stop the timer."); + Assert.IsNull(triggeredValue, "Expected result should be no value set."); + Assert.AreEqual(0, triggeredCount, "Expected not to have code run."); + Assert.AreEqual(0, customTriggeredCount, "Expected not to have custom code run."); + + // Wait until timer would have fired + await Task.Delay(TimeSpan.FromMilliseconds(60)); + + Assert.AreEqual(false, debounceTimer.IsRunning, "Expected the timer to remain stopped."); + Assert.IsNull(triggeredValue, "Expected result should still be no value set."); + Assert.AreEqual(0, triggeredCount, "Expected not to have code run still."); + Assert.AreEqual(0, customTriggeredCount, "Expected not to have custom code run still."); + } + + [TestCategory("DispatcherQueueTimerExtensions")] + [UIThreadTestMethod] + public async Task DispatcherQueueTimer_Debounce_Trailing_Interrupt() + { + var debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); + + // Test custom event handler too + var customTriggeredCount = 0; + debounceTimer.Tick += (s, o) => + { + customTriggeredCount++; + }; + + var triggeredCount = 0; + string? triggeredValue = null; + + var value = "He"; + debounceTimer.Debounce( + () => + { + triggeredCount++; + triggeredValue = value; + }, + TimeSpan.FromMilliseconds(60)); + + Assert.AreEqual(true, debounceTimer.IsRunning, "Expected time to be running."); + Assert.AreEqual(0, triggeredCount, "Function shouldn't have run yet."); + Assert.AreEqual(0, customTriggeredCount, "Custom Function shouldn't have run yet."); Assert.IsNull(triggeredValue, "Function shouldn't have run yet."); var value2 = "Hello"; @@ -54,20 +148,72 @@ public async Task DispatcherQueueTimer_Debounce_Interrupt() TimeSpan.FromMilliseconds(60)); Assert.AreEqual(true, debounceTimer.IsRunning, "Expected time to be running."); + Assert.AreEqual(0, triggeredCount, "Function shouldn't have run yet."); + Assert.AreEqual(0, customTriggeredCount, "Custom Function shouldn't have run yet."); + Assert.IsNull(triggeredValue, "Function shouldn't have run yet."); await Task.Delay(TimeSpan.FromMilliseconds(110)); Assert.AreEqual(false, debounceTimer.IsRunning, "Expected to stop the timer."); Assert.AreEqual(value2, triggeredValue, "Expected to execute the last action."); Assert.AreEqual(1, triggeredCount, "Expected to postpone execution."); + Assert.AreEqual(1, customTriggeredCount, "Expected to postpone execution of custom event handler."); } + [TestCategory("DispatcherQueueTimerExtensions")] + [UIThreadTestMethod] + public async Task DispatcherQueueTimer_Debounce_Immediate() + { + var debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); + + // Test custom event handler too + var customTriggeredCount = 0; + debounceTimer.Tick += (s, o) => + { + customTriggeredCount++; + }; + + var triggeredCount = 0; + string? triggeredValue = null; + + var value = "He"; + debounceTimer.Debounce( + () => + { + triggeredCount++; + triggeredValue = value; + }, + TimeSpan.FromMilliseconds(60), true); + + Assert.AreEqual(true, debounceTimer.IsRunning, "Expected time to be running."); + Assert.AreEqual(1, triggeredCount, "Function should have run right away."); + Assert.AreEqual(0, customTriggeredCount, "Custom Function won't have run as cooldown hasn't elapsed."); + Assert.AreEqual(value, triggeredValue, "Should have expected immediate set of value"); + + await Task.Delay(TimeSpan.FromMilliseconds(80)); + + Assert.AreEqual(false, debounceTimer.IsRunning, "Expected to stop the timer."); + Assert.AreEqual(1, customTriggeredCount, "Custom Function should have run now that cooldown expired."); + } + + /// + /// Tests the immediate mode of the Debounce function ignoring subsequent inputs that come after the first within the specified time window. + /// + /// For instance, this could be useful to ignore extra multiple clicks on a button, but immediately start processing upon the first click. + /// [TestCategory("DispatcherQueueTimerExtensions")] [UIThreadTestMethod] public async Task DispatcherQueueTimer_Debounce_Immediate_Interrupt() { var debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); + // Test custom event handler too + var customTriggeredCount = 0; + debounceTimer.Tick += (s, o) => + { + customTriggeredCount++; + }; + var triggeredCount = 0; string? triggeredValue = null; @@ -82,6 +228,7 @@ public async Task DispatcherQueueTimer_Debounce_Immediate_Interrupt() Assert.AreEqual(true, debounceTimer.IsRunning, "Expected time to be running."); Assert.AreEqual(1, triggeredCount, "Function should have run right away."); + Assert.AreEqual(0, customTriggeredCount, "Custom Function should not have run as cooldown hasn't expired."); Assert.AreEqual(value, triggeredValue, "Should have expected immediate set of value"); var value2 = "Hello"; @@ -91,14 +238,210 @@ public async Task DispatcherQueueTimer_Debounce_Immediate_Interrupt() triggeredCount++; triggeredValue = value2; }, - TimeSpan.FromMilliseconds(60)); + TimeSpan.FromMilliseconds(60), true); // Ensure we're interrupting with immediate again Assert.AreEqual(true, debounceTimer.IsRunning, "Expected time to be running."); + Assert.AreEqual(1, triggeredCount, "2nd request coming within first period should have been ignored."); + Assert.AreEqual(0, customTriggeredCount, "Cooldown should be reset, so we still shouldn't have fired Tick."); + Assert.AreEqual(value, triggeredValue, "Value shouldn't have changed from 2nd request within time bound."); + // Wait for cooldown to expire await Task.Delay(TimeSpan.FromMilliseconds(110)); Assert.AreEqual(false, debounceTimer.IsRunning, "Expected to stop the timer."); - Assert.AreEqual(value2, triggeredValue, "Expected to execute the last action."); - Assert.AreEqual(2, triggeredCount, "Expected to postpone execution."); + Assert.AreEqual(value, triggeredValue, "Expected to execute only the first action."); + Assert.AreEqual(1, triggeredCount, "Expected 2nd request to be ignored."); + Assert.AreEqual(1, customTriggeredCount, "Custom should have run now that cooldown expired."); + } + + /// + /// Tests the scenario where we flip from wanting trailing to leading edge invocation. + /// + /// For instance, this could be for a case where a user has cleared the textbox, so you + /// want to immediately return new results vs. waiting for further input. + /// + [TestCategory("DispatcherQueueTimerExtensions")] + [UIThreadTestMethod] + public async Task DispatcherQueueTimer_Debounce_Trailing_Switch_Leading_Interrupt() + { + var debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); + + // Test custom event handler too + var customTriggeredCount = 0; + debounceTimer.Tick += (s, o) => + { + customTriggeredCount++; + }; + + var triggeredCount = 0; + string? triggeredValue = null; + + var value = "Hello"; + debounceTimer.Debounce( + () => + { + triggeredCount++; + triggeredValue = value; + }, + TimeSpan.FromMilliseconds(100), false); // Start off waiting + + // Intentional pause to mimic reality + await Task.Delay(TimeSpan.FromMilliseconds(30)); + + Assert.AreEqual(true, debounceTimer.IsRunning, "Expected time to be running."); + Assert.AreEqual(0, triggeredCount, "Function shouldn't have run yet."); + Assert.AreEqual(0, customTriggeredCount, "Custom Function shouldn't have run yet."); + Assert.IsNull(triggeredValue, "Function shouldn't have run yet."); + + // Now interrupt with a scenario we want processed immediately, i.e. user started typing something new + var value2 = "He"; + debounceTimer.Debounce( + () => + { + triggeredCount++; + triggeredValue = value2; + }, + TimeSpan.FromMilliseconds(100), true); + + Assert.AreEqual(true, debounceTimer.IsRunning, "Expected timer should still be running."); + Assert.AreEqual(1, triggeredCount, "Function should now have run immediately."); + Assert.AreEqual(0, customTriggeredCount, "Custom Function still shouldn't have run yet."); + Assert.AreEqual(value2, triggeredValue, "Function should have set value to 'He'"); + + // Wait to where all should be done + await Task.Delay(TimeSpan.FromMilliseconds(120)); + + Assert.AreEqual(false, debounceTimer.IsRunning, "Expected to stop the timer."); + Assert.AreEqual(value2, triggeredValue, "Expected value to remain the same."); + Assert.AreEqual(1, triggeredCount, "Expected to interrupt execution and ignore initial queued execution."); + Assert.AreEqual(1, customTriggeredCount, "Custom function should have run once at end of leading cooldown."); + } + + /// + /// Tests where we start with immediately processing a delay, then switch to processing after. + /// + /// For instance, maybe we want to ensure we start processing the first letter of a search query to filter initial results. Then later, we might want to delay and wait to execute until all the query string is available. + /// + [TestCategory("DispatcherQueueTimerExtensions")] + [UIThreadTestMethod] + public async Task DispatcherQueueTimer_Debounce_Leading_Switch_Trailing_Interrupt_Twice() + { + var debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); + + // Test custom event handler too + var customTriggeredCount = 0; + debounceTimer.Tick += (s, o) => + { + customTriggeredCount++; + }; + + var triggeredCount = 0; + string? triggeredValue = null; + + var value = "H"; + debounceTimer.Debounce( + () => + { + triggeredCount++; + triggeredValue = value; + }, + TimeSpan.FromMilliseconds(100), true); // Start off right away + + Assert.AreEqual(true, debounceTimer.IsRunning, "Expected time to be running."); + Assert.AreEqual(1, triggeredCount, "Function should have run right away."); + Assert.AreEqual(0, customTriggeredCount, "Custom Function should not run right away on leading."); + Assert.AreEqual(value, triggeredValue, "Function should have set value immediately."); + + // Pragmatic pause + await Task.Delay(TimeSpan.FromMilliseconds(30)); + + // Now interrupt with more data two times. + var value2 = "Hel"; + debounceTimer.Debounce( + () => + { + triggeredCount++; + triggeredValue = value2; + }, + TimeSpan.FromMilliseconds(100), false); // We want to ensure we catch the latest data now + + Assert.AreEqual(true, debounceTimer.IsRunning, "Expected timer to still to be running."); + Assert.AreEqual(1, triggeredCount, "Function should now haven't run again yet."); + Assert.AreEqual(0, customTriggeredCount, "Custom Function should not run right away on switch to trailing either."); + Assert.AreEqual(value, triggeredValue, "Function should still be the initial value"); + + // Pragmatic pause again + await Task.Delay(TimeSpan.FromMilliseconds(30)); + + var value3 = "Hello"; + debounceTimer.Debounce( + () => + { + triggeredCount++; + triggeredValue = value3; + }, + TimeSpan.FromMilliseconds(100), false); // We want to ensure we catch the latest data now + + Assert.AreEqual(true, debounceTimer.IsRunning, "Expected timer to still to be running."); + Assert.AreEqual(1, triggeredCount, "Function should still now haven't run again yet."); + Assert.AreEqual(0, customTriggeredCount, "Custom Function should not run yet, as not enough time passed."); + Assert.AreEqual(value, triggeredValue, "Function should still be the initial value x2"); + + // Wait to where the timer should have fired and is done + await Task.Delay(TimeSpan.FromMilliseconds(120)); + + Assert.AreEqual(false, debounceTimer.IsRunning, "Expected timer to stopped at trailing edge to execute latest result."); + Assert.AreEqual(value3, triggeredValue, "Expected value to now be the last value provided."); + Assert.AreEqual(2, triggeredCount, "Expected to interrupt execution of 2nd request."); + Assert.AreEqual(1, customTriggeredCount, "Custom Function should have run once at end of trailing debounce."); + } + + [TestCategory("DispatcherQueueTimerExtensions")] + [UIThreadTestMethod] + public async Task DispatcherQueueTimer_Debounce_Trailing_Stop_Lifetime() + { + // Our test indicator + WeakReference? reference = null; + + // Still need to capture this on our UI thread + DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread(); + + await Task.Run(() => + { + // This test checks the lifetime of the timer and if we hold a reference to it. + var debounceTimer = _queue.CreateTimer(); + + // Track the DispatcherQueueTimer object + reference = new WeakReference(debounceTimer, true); + + var triggeredCount = 0; + string? triggeredValue = null; + + var value = "He"; + debounceTimer.Debounce( + () => + { + triggeredCount++; + triggeredValue = value; + }, + TimeSpan.FromMilliseconds(60)); + + // Stop the timer before it would fire, with our proper method to clean-up + debounceTimer.Stop(); + + Assert.AreEqual(false, debounceTimer.IsRunning, "Expected to stop the timer."); + + debounceTimer = null; + }); + + // Now out of scope and see if GC cleans up + GC.Collect(); + GC.WaitForPendingFinalizers(); + + // Clean-up any UI thread work + await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { }); + + Assert.IsNotNull(reference, "Didn't capture weak reference."); + Assert.IsNull(reference.Target, "Strong reference to DispatcherQueueTimer still exists."); } } diff --git a/components/Helpers/src/CameraHelper/CameraHelper.cs b/components/Helpers/src/CameraHelper/CameraHelper.cs index 831d89f6..3f166791 100644 --- a/components/Helpers/src/CameraHelper/CameraHelper.cs +++ b/components/Helpers/src/CameraHelper/CameraHelper.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Helpers; /// Make sure you have the capability webcam enabled for your app to access the device's camera. /// #pragma warning disable CA1063 // Implement IDisposable Correctly -public class CameraHelper : IDisposable +public partial class CameraHelper : IDisposable { private static IReadOnlyList? _frameSourceGroups; #pragma warning disable CA2213 // Disposable fields should be disposed diff --git a/components/Helpers/src/CommunityToolkit.WinUI.Helpers.csproj b/components/Helpers/src/CommunityToolkit.WinUI.Helpers.csproj index c586beef..2f692ab1 100644 --- a/components/Helpers/src/CommunityToolkit.WinUI.Helpers.csproj +++ b/components/Helpers/src/CommunityToolkit.WinUI.Helpers.csproj @@ -8,6 +8,7 @@ CommunityToolkit.WinUI.HelpersRns ReadMe.md + true diff --git a/components/Helpers/src/ThemeListener.cs b/components/Helpers/src/ThemeListener.cs index af35e3d9..ecd04b13 100644 --- a/components/Helpers/src/ThemeListener.cs +++ b/components/Helpers/src/ThemeListener.cs @@ -26,7 +26,7 @@ namespace CommunityToolkit.WinUI.Helpers; /// and Signals an Event when they occur. /// [AllowForWeb] -public sealed class ThemeListener : IDisposable +public sealed partial class ThemeListener : IDisposable { /// /// Gets the Name of the Current Theme. diff --git a/components/ImageCropper/src/Dependencies.props b/components/ImageCropper/src/Dependencies.props index 92911a92..682bbcdd 100644 --- a/components/ImageCropper/src/Dependencies.props +++ b/components/ImageCropper/src/Dependencies.props @@ -11,7 +11,7 @@ - + @@ -21,7 +21,7 @@ - + diff --git a/components/ImageCropper/src/ImageCropper.Helpers.cs b/components/ImageCropper/src/ImageCropper.Helpers.cs index 30166c77..280d31d7 100644 --- a/components/ImageCropper/src/ImageCropper.Helpers.cs +++ b/components/ImageCropper/src/ImageCropper.Helpers.cs @@ -36,7 +36,20 @@ private static async Task CropImageAsync(WriteableBitmap writeableBitmap, IRando using (var sourceStream = writeableBitmap.PixelBuffer.AsStream()) { var buffer = new byte[sourceStream.Length]; - await sourceStream.ReadAsync(buffer, 0, buffer.Length); + var pos = 0; + + while (pos < buffer.Length) + { + var remaining = buffer.Length - pos; + var read = await sourceStream.ReadAsync(buffer, pos, remaining); + if (read == 0) + { + break; + } + + pos += read; + } + var bitmapEncoder = await BitmapEncoder.CreateAsync(GetEncoderId(bitmapFileFormat), stream); bitmapEncoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, (uint)writeableBitmap.PixelWidth, (uint)writeableBitmap.PixelHeight, 96.0, 96.0, buffer); bitmapEncoder.BitmapTransform.Bounds = new BitmapBounds diff --git a/components/ImageCropper/src/ImageCropperThumb.cs b/components/ImageCropper/src/ImageCropperThumb.cs index d79a7dbe..8036dc8f 100644 --- a/components/ImageCropper/src/ImageCropperThumb.cs +++ b/components/ImageCropper/src/ImageCropperThumb.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// The control is used for . /// -public class ImageCropperThumb : Control +public partial class ImageCropperThumb : Control { private readonly TranslateTransform _layoutTransform = new(); internal const string NormalState = "Normal"; diff --git a/components/Media/samples/AttachedCardShadow.md b/components/Media/samples/AttachedCardShadow.md index 58420d95..bc36f5ae 100644 --- a/components/Media/samples/AttachedCardShadow.md +++ b/components/Media/samples/AttachedCardShadow.md @@ -12,7 +12,7 @@ issue-id: 0 icon: Assets/Shadow.png --- -The `AttachedCardShadow` is the easiest to use and most performant shadow. It is recommended to use it where possible, if taking a Win2D dependency is not a concern. It's only drawbacks are the extra dependency required and that it only supports rectangular and rounded-rectangular geometries (as described in the table above). +The `AttachedCardShadow` is the easiest to use and most performant shadow. It is recommended to use it where possible, if taking a Win2D dependency is not a concern. Its only drawbacks are the extra dependency required and that it only supports rectangular and rounded-rectangular geometries (as described in the table above). The great benefit to the `AttachedCardShadow` is that no extra surface or element is required to add the shadow. This reduces the complexity required in development and allows shadows to easily be added at any point in the development process. It also supports transparent elements, without displaying the shadow behind them! diff --git a/components/Media/samples/BlurEffectAnimationSample.xaml b/components/Media/samples/BlurEffectAnimationSample.xaml index f2abd267..b998e48d 100644 --- a/components/Media/samples/BlurEffectAnimationSample.xaml +++ b/components/Media/samples/BlurEffectAnimationSample.xaml @@ -3,7 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ani="using:CommunityToolkit.WinUI.Animations" xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" - xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:media="using:CommunityToolkit.WinUI.Media"> @@ -38,9 +37,9 @@ - + - + diff --git a/components/Media/samples/ColorEffectAnimationSample.xaml b/components/Media/samples/ColorEffectAnimationSample.xaml index f5251dc8..6aad5cfc 100644 --- a/components/Media/samples/ColorEffectAnimationSample.xaml +++ b/components/Media/samples/ColorEffectAnimationSample.xaml @@ -3,7 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ani="using:CommunityToolkit.WinUI.Animations" xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" - xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:media="using:CommunityToolkit.WinUI.Media"> @@ -38,9 +37,9 @@ - + - + diff --git a/components/Media/samples/CrossFadeEffectAnimationSample.xaml b/components/Media/samples/CrossFadeEffectAnimationSample.xaml index ecb49439..d31013b9 100644 --- a/components/Media/samples/CrossFadeEffectAnimationSample.xaml +++ b/components/Media/samples/CrossFadeEffectAnimationSample.xaml @@ -3,7 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ani="using:CommunityToolkit.WinUI.Animations" xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" - xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:media="using:CommunityToolkit.WinUI.Media"> @@ -43,9 +42,9 @@ - + - + diff --git a/components/Media/samples/EffectAnimationsSample.xaml b/components/Media/samples/EffectAnimationsSample.xaml index b6ad850e..56c1f7e7 100644 --- a/components/Media/samples/EffectAnimationsSample.xaml +++ b/components/Media/samples/EffectAnimationsSample.xaml @@ -3,7 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:animations="using:CommunityToolkit.WinUI.Animations" xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" - xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:media="using:CommunityToolkit.WinUI.Media"> @@ -74,9 +73,9 @@ - + - + diff --git a/components/Media/samples/ExposureEffectAnimationSample.xaml b/components/Media/samples/ExposureEffectAnimationSample.xaml index df3f5661..6a40e928 100644 --- a/components/Media/samples/ExposureEffectAnimationSample.xaml +++ b/components/Media/samples/ExposureEffectAnimationSample.xaml @@ -3,7 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ani="using:CommunityToolkit.WinUI.Animations" xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" - xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:media="using:CommunityToolkit.WinUI.Media"> @@ -39,9 +38,9 @@ - + - + diff --git a/components/Media/samples/HueRotationEffectAnimationSample.xaml b/components/Media/samples/HueRotationEffectAnimationSample.xaml index b10ecdb5..f31e89e4 100644 --- a/components/Media/samples/HueRotationEffectAnimationSample.xaml +++ b/components/Media/samples/HueRotationEffectAnimationSample.xaml @@ -3,7 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ani="using:CommunityToolkit.WinUI.Animations" xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" - xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:media="using:CommunityToolkit.WinUI.Media"> @@ -39,9 +38,9 @@ - + - + diff --git a/components/Media/samples/OpacityEffectAnimationSample.xaml b/components/Media/samples/OpacityEffectAnimationSample.xaml index d992528b..5675ca73 100644 --- a/components/Media/samples/OpacityEffectAnimationSample.xaml +++ b/components/Media/samples/OpacityEffectAnimationSample.xaml @@ -3,7 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ani="using:CommunityToolkit.WinUI.Animations" xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" - xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:media="using:CommunityToolkit.WinUI.Media"> @@ -35,9 +34,9 @@ - + - + diff --git a/components/Media/samples/SaturationEffectAnimationSample.xaml b/components/Media/samples/SaturationEffectAnimationSample.xaml index 5c147dab..3f1cfd9f 100644 --- a/components/Media/samples/SaturationEffectAnimationSample.xaml +++ b/components/Media/samples/SaturationEffectAnimationSample.xaml @@ -3,7 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ani="using:CommunityToolkit.WinUI.Animations" xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" - xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:media="using:CommunityToolkit.WinUI.Media"> @@ -38,9 +37,9 @@ - + - + diff --git a/components/Media/samples/SepiaEffectAnimationSample.xaml b/components/Media/samples/SepiaEffectAnimationSample.xaml index 22647581..dcb4c45e 100644 --- a/components/Media/samples/SepiaEffectAnimationSample.xaml +++ b/components/Media/samples/SepiaEffectAnimationSample.xaml @@ -3,7 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ani="using:CommunityToolkit.WinUI.Animations" xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" - xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:media="using:CommunityToolkit.WinUI.Media"> @@ -38,9 +37,9 @@ - + - + diff --git a/components/Media/src/Brushes/AcrylicBrush.cs b/components/Media/src/Brushes/AcrylicBrush.cs index ba95daea..272764c4 100644 --- a/components/Media/src/Brushes/AcrylicBrush.cs +++ b/components/Media/src/Brushes/AcrylicBrush.cs @@ -2,17 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if WINUI2 using CommunityToolkit.WinUI.Media.Pipelines; +#if WINUI3 +using Microsoft.UI.Composition; +#endif using Windows.UI; +#if WINUI2 using Windows.UI.Composition; +#endif namespace CommunityToolkit.WinUI.Media; /// /// A that implements an acrylic effect with customizable parameters /// -public sealed class AcrylicBrush : XamlCompositionEffectBrushBase +public sealed partial class AcrylicBrush : XamlCompositionEffectBrushBase { /// /// The instance in use to set the blur amount @@ -30,6 +34,7 @@ public sealed class AcrylicBrush : XamlCompositionEffectBrushBase /// private EffectSetter? tintOpacitySetter; +#if WINUI2 /// /// Gets or sets the background source mode for the effect (the default is ). /// @@ -62,11 +67,18 @@ private static void OnSourcePropertyChanged(DependencyObject d, DependencyProper brush.OnConnected(); } } +#endif +#if WINUI2 /// /// Gets or sets the blur amount for the effect (must be a positive value) /// /// This property is ignored when the active mode is +#else + /// + /// Gets or sets the blur amount for the effect (must be a positive value) + /// +#endif public double BlurAmount { get => (double)GetValue(BlurAmountProperty); @@ -90,7 +102,9 @@ public double BlurAmount private static void OnBlurAmountPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is AcrylicBrush brush && +#if WINUI2 brush.BackgroundSource != AcrylicBackgroundSource.HostBackdrop && // Blur is fixed by OS when using HostBackdrop source. +#endif brush.CompositionBrush is CompositionBrush target) { brush.blurAmountSetter?.Invoke(target, (float)(double)e.NewValue); @@ -197,6 +211,7 @@ private static void OnTextureUriPropertyChanged(DependencyObject d, DependencyPr /// protected override PipelineBuilder OnPipelineRequested() { +#if WINUI2 switch (BackgroundSource) { case AcrylicBackgroundSource.Backdrop: @@ -217,6 +232,15 @@ protected override PipelineBuilder OnPipelineRequested() TextureUri); default: throw new ArgumentOutOfRangeException(nameof(BackgroundSource), $"Invalid acrylic source: {BackgroundSource}"); } +#else + return PipelineBuilder.FromBackdropAcrylic( + TintColor, + out this.tintColorSetter, + (float)TintOpacity, + out this.tintOpacitySetter, + (float)BlurAmount, + out blurAmountSetter, + TextureUri); +#endif } } -#endif diff --git a/components/Media/src/Brushes/BackdropBlurBrush.cs b/components/Media/src/Brushes/BackdropBlurBrush.cs index b6159604..b22db802 100644 --- a/components/Media/src/Brushes/BackdropBlurBrush.cs +++ b/components/Media/src/Brushes/BackdropBlurBrush.cs @@ -17,7 +17,7 @@ namespace CommunityToolkit.WinUI.Media; /// /// The is a that blurs whatever is behind it in the application. /// -public class BackdropBlurBrush : XamlCompositionEffectBrushBase +public partial class BackdropBlurBrush : XamlCompositionEffectBrushBase { /// /// The instance currently in use diff --git a/components/Media/src/Brushes/BackdropGammaTransferBrush.cs b/components/Media/src/Brushes/BackdropGammaTransferBrush.cs index c0129f64..a438e3b1 100644 --- a/components/Media/src/Brushes/BackdropGammaTransferBrush.cs +++ b/components/Media/src/Brushes/BackdropGammaTransferBrush.cs @@ -15,7 +15,7 @@ namespace CommunityToolkit.WinUI.Media; /// /// A brush which alters the colors of whatever is behind it in the application by applying a per-channel gamma transfer function. See https://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_GammaTransferEffect.htm. /// -public class BackdropGammaTransferBrush : XamlCompositionBrushBase +public partial class BackdropGammaTransferBrush : XamlCompositionBrushBase { /// /// Gets or sets the amount of scale to apply to the alpha chennel. diff --git a/components/Media/src/Brushes/BackdropInvertBrush.cs b/components/Media/src/Brushes/BackdropInvertBrush.cs index b5cc729e..7d1bf0a2 100644 --- a/components/Media/src/Brushes/BackdropInvertBrush.cs +++ b/components/Media/src/Brushes/BackdropInvertBrush.cs @@ -11,7 +11,7 @@ namespace CommunityToolkit.WinUI.Media; /// /// The is a which inverts whatever is behind it in the application. /// -public class BackdropInvertBrush : XamlCompositionEffectBrushBase +public partial class BackdropInvertBrush : XamlCompositionEffectBrushBase { /// protected override PipelineBuilder OnPipelineRequested() diff --git a/components/Media/src/Brushes/BackdropSaturationBrush.cs b/components/Media/src/Brushes/BackdropSaturationBrush.cs index f965bb2e..0eb9242b 100644 --- a/components/Media/src/Brushes/BackdropSaturationBrush.cs +++ b/components/Media/src/Brushes/BackdropSaturationBrush.cs @@ -15,7 +15,7 @@ namespace CommunityToolkit.WinUI.Media; /// /// Brush which applies a SaturationEffect to the Backdrop. http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_SaturationEffect.htm /// -public class BackdropSaturationBrush : XamlCompositionEffectBrushBase +public partial class BackdropSaturationBrush : XamlCompositionEffectBrushBase { /// /// The instance currently in use diff --git a/components/Media/src/Brushes/BackdropSepiaBrush.cs b/components/Media/src/Brushes/BackdropSepiaBrush.cs index 73f28a36..68180b3d 100644 --- a/components/Media/src/Brushes/BackdropSepiaBrush.cs +++ b/components/Media/src/Brushes/BackdropSepiaBrush.cs @@ -15,7 +15,7 @@ namespace CommunityToolkit.WinUI.Media; /// /// Brush which applies a SepiaEffect to the Backdrop. http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_SepiaEffect.htm /// -public class BackdropSepiaBrush : XamlCompositionEffectBrushBase +public partial class BackdropSepiaBrush : XamlCompositionEffectBrushBase { /// /// The instance currently in use diff --git a/components/Media/src/Brushes/ImageBlendBrush.cs b/components/Media/src/Brushes/ImageBlendBrush.cs index 8f541a46..368ba267 100644 --- a/components/Media/src/Brushes/ImageBlendBrush.cs +++ b/components/Media/src/Brushes/ImageBlendBrush.cs @@ -20,7 +20,7 @@ namespace CommunityToolkit.WinUI.Media; /// /// Brush which blends a to the Backdrop in a given mode. See http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffect.htm. /// -public class ImageBlendBrush : XamlCompositionBrushBase +public partial class ImageBlendBrush : XamlCompositionBrushBase { private LoadedImageSurface? _surface; private CompositionSurfaceBrush? _surfaceBrush; diff --git a/components/Media/src/Brushes/PipelineBrush.cs b/components/Media/src/Brushes/PipelineBrush.cs index 8267c7dd..1122f72b 100644 --- a/components/Media/src/Brushes/PipelineBrush.cs +++ b/components/Media/src/Brushes/PipelineBrush.cs @@ -10,7 +10,7 @@ namespace CommunityToolkit.WinUI.Media; /// A that renders a customizable Composition/Win2D effects pipeline /// [ContentProperty(Name = nameof(Effects))] -public sealed class PipelineBrush : XamlCompositionEffectBrushBase +public sealed partial class PipelineBrush : XamlCompositionEffectBrushBase { /// /// Gets or sets the source for the current pipeline (defaults to a with source). diff --git a/components/Media/src/Brushes/TilesBrush.cs b/components/Media/src/Brushes/TilesBrush.cs index 894e98ab..b37ca066 100644 --- a/components/Media/src/Brushes/TilesBrush.cs +++ b/components/Media/src/Brushes/TilesBrush.cs @@ -9,7 +9,7 @@ namespace CommunityToolkit.WinUI.Media; /// /// A that displays a tiled image /// -public sealed class TilesBrush : XamlCompositionEffectBrushBase +public sealed partial class TilesBrush : XamlCompositionEffectBrushBase { /// /// Gets or sets the to the texture to use diff --git a/components/Media/src/Brushes/XamlCompositionBrush.cs b/components/Media/src/Brushes/XamlCompositionBrush.cs index 2b507da9..2df79241 100644 --- a/components/Media/src/Brushes/XamlCompositionBrush.cs +++ b/components/Media/src/Brushes/XamlCompositionBrush.cs @@ -29,7 +29,7 @@ public delegate Task XamlEffectAnimation(T value, TimeSpan duration) /// /// A simple that can be used to quickly create XAML brushes from arbitrary pipelines /// -public sealed class XamlCompositionBrush : XamlCompositionEffectBrushBase +public sealed partial class XamlCompositionBrush : XamlCompositionEffectBrushBase { /// /// Gets the pipeline for the current instance diff --git a/components/Media/src/CommunityToolkit.WinUI.Media.csproj b/components/Media/src/CommunityToolkit.WinUI.Media.csproj index 21ff99fd..4a52a160 100644 --- a/components/Media/src/CommunityToolkit.WinUI.Media.csproj +++ b/components/Media/src/CommunityToolkit.WinUI.Media.csproj @@ -8,6 +8,7 @@ CommunityToolkit.WinUI.MediaRns ReadMe.md + true diff --git a/components/Media/src/Dependencies.props b/components/Media/src/Dependencies.props index da3896da..7b27ab73 100644 --- a/components/Media/src/Dependencies.props +++ b/components/Media/src/Dependencies.props @@ -1,5 +1,5 @@ - - + + + + - + diff --git a/components/Media/src/Effects/Extensions/AcrylicSourceExtension.cs b/components/Media/src/Effects/Extensions/AcrylicSourceExtension.cs index cb555ab2..2ac3f955 100644 --- a/components/Media/src/Effects/Extensions/AcrylicSourceExtension.cs +++ b/components/Media/src/Effects/Extensions/AcrylicSourceExtension.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Media; /// /// This effect mirrors the look of the default implementation [MarkupExtensionReturnType(ReturnType = typeof(PipelineBuilder))] -public sealed class AcrylicSourceExtension : MarkupExtension +public sealed partial class AcrylicSourceExtension : MarkupExtension { /// /// Gets or sets the background source mode for the effect (the default is ). diff --git a/components/Media/src/Effects/Extensions/BackdropSourceExtension.cs b/components/Media/src/Effects/Extensions/BackdropSourceExtension.cs index d969b82a..266d8563 100644 --- a/components/Media/src/Effects/Extensions/BackdropSourceExtension.cs +++ b/components/Media/src/Effects/Extensions/BackdropSourceExtension.cs @@ -11,7 +11,7 @@ namespace CommunityToolkit.WinUI.Media; /// A backdrop effect that can sample from a specified source /// [MarkupExtensionReturnType(ReturnType = typeof(PipelineBuilder))] -public sealed class BackdropSourceExtension : MarkupExtension +public sealed partial class BackdropSourceExtension : MarkupExtension { #if WINUI2 /// Gets or sets the background source mode for the effect (the default is ). diff --git a/components/Media/src/Effects/Extensions/ImageSourceExtension.cs b/components/Media/src/Effects/Extensions/ImageSourceExtension.cs index 8c04b7a9..011a5caf 100644 --- a/components/Media/src/Effects/Extensions/ImageSourceExtension.cs +++ b/components/Media/src/Effects/Extensions/ImageSourceExtension.cs @@ -9,7 +9,7 @@ namespace CommunityToolkit.WinUI.Media; /// /// An image effect, which displays an image loaded as a Win2D surface /// -public sealed class ImageSourceExtension : ImageSourceBaseExtension +public sealed partial class ImageSourceExtension : ImageSourceBaseExtension { /// protected override object ProvideValue() diff --git a/components/Media/src/Effects/Extensions/SolidColorSourceExtension.cs b/components/Media/src/Effects/Extensions/SolidColorSourceExtension.cs index 1f713f44..69565092 100644 --- a/components/Media/src/Effects/Extensions/SolidColorSourceExtension.cs +++ b/components/Media/src/Effects/Extensions/SolidColorSourceExtension.cs @@ -11,7 +11,7 @@ namespace CommunityToolkit.WinUI.Media; /// An effect that renders a standard 8bit SDR color on the available surface /// [MarkupExtensionReturnType(ReturnType = typeof(PipelineBuilder))] -public sealed class SolidColorSourceExtension : MarkupExtension +public sealed partial class SolidColorSourceExtension : MarkupExtension { /// /// Gets or sets the color to display diff --git a/components/Media/src/Effects/Extensions/TileSourceExtension.cs b/components/Media/src/Effects/Extensions/TileSourceExtension.cs index a68956ee..95269f68 100644 --- a/components/Media/src/Effects/Extensions/TileSourceExtension.cs +++ b/components/Media/src/Effects/Extensions/TileSourceExtension.cs @@ -10,7 +10,7 @@ namespace CommunityToolkit.WinUI.Media; /// An effect that loads an image and replicates it to cover all the available surface area /// /// This effect maps to the Win2D effect -public sealed class TileSourceExtension : ImageSourceBaseExtension +public sealed partial class TileSourceExtension : ImageSourceBaseExtension { /// protected override object ProvideValue() diff --git a/components/Media/src/Extensions/System.Threading.Tasks/AsyncMutex.cs b/components/Media/src/Extensions/System.Threading.Tasks/AsyncMutex.cs index d30e29ac..cc6e47cc 100644 --- a/components/Media/src/Extensions/System.Threading.Tasks/AsyncMutex.cs +++ b/components/Media/src/Extensions/System.Threading.Tasks/AsyncMutex.cs @@ -10,7 +10,9 @@ namespace CommunityToolkit.WinUI.Media; /// An implementation that can be easily used inside a block /// #pragma warning disable CA1001 // Types that own disposable fields should be disposable +#pragma warning disable CsWinRT1028 // Partial not required for types never passed to native WinRT method. internal sealed class AsyncMutex +#pragma warning restore CsWinRT1028 #pragma warning restore CA1001 // Types that own disposable fields should be disposable { /// @@ -32,7 +34,9 @@ public async Task LockAsync() /// /// Private class that implements the automatic release of the semaphore /// +#pragma warning disable CsWinRT1028 // Partial not required for types never passed to native WinRT method. private sealed class Lock : IDisposable +#pragma warning restore CsWinRT1028 { /// /// The instance of the parent class diff --git a/components/Primitives/samples/Primitives.Samples.csproj b/components/Primitives/samples/Primitives.Samples.csproj index 3927b93a..63350207 100644 --- a/components/Primitives/samples/Primitives.Samples.csproj +++ b/components/Primitives/samples/Primitives.Samples.csproj @@ -63,22 +63,4 @@ PreserveNewest - - - - UniformGridSample.xaml - - - DockPanelSample.xaml - - - ConstrainedBoxSample.xaml - - - StaggeredLayoutSample.xaml - - - WrapPanelSample.xaml - - diff --git a/components/Primitives/samples/SwitchPresenter.md b/components/Primitives/samples/SwitchPresenter.md index 471de5a6..2c3590bc 100644 --- a/components/Primitives/samples/SwitchPresenter.md +++ b/components/Primitives/samples/SwitchPresenter.md @@ -29,3 +29,15 @@ Or it can simply be used to clearly display different outcomes based on some sta `SwitchPresenter` can also be used as a replacement for the deprecated `Loading` control. This provides more fine-grained control over animations and content within each state: > [!SAMPLE SwitchPresenterLoaderSample] + +We can also invert the paradigm a bit with a `SwitchPresenter` to do data transformations within XAML using a `ContentTemplate`. Imagine an alternate view of our first starting example: + +> [!SAMPLE SwitchPresenterTemplateSample] + +That's right! `SwitchPresenter` can be used not just for displaying different UIElements but in feeding different kinds of data into the `ContentTemplate` as well. + +## SwitchConverter + +A new analog to `SwitchPresenter` is the `SwitchConverter` which can be used in bindings to translate values into resources: + +> [!SAMPLE SwitchConverterBrushSample] diff --git a/components/Primitives/samples/SwitchPresenter/SwitchConverterBrushSample.xaml b/components/Primitives/samples/SwitchPresenter/SwitchConverterBrushSample.xaml new file mode 100644 index 00000000..400ebcb0 --- /dev/null +++ b/components/Primitives/samples/SwitchPresenter/SwitchConverterBrushSample.xaml @@ -0,0 +1,45 @@ + + + + + Warning + + + + + + + + + + + + + + Success + Warning + Error + + + + + diff --git a/components/Primitives/samples/SwitchPresenter/SwitchConverterBrushSample.xaml.cs b/components/Primitives/samples/SwitchPresenter/SwitchConverterBrushSample.xaml.cs new file mode 100644 index 00000000..c8f259a0 --- /dev/null +++ b/components/Primitives/samples/SwitchPresenter/SwitchConverterBrushSample.xaml.cs @@ -0,0 +1,23 @@ +// 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.Converters; + +namespace PrimitivesExperiment.Samples.SwitchPresenter; + +[ToolkitSample(id: nameof(SwitchConverterBrushSample), "SwitchConverter Brush", description: $"A sample for showing how to use a {nameof(SwitchConverter)} for swapping a brush based on an enum.")] +public sealed partial class SwitchConverterBrushSample : Page +{ + public SwitchConverterBrushSample() + { + this.InitializeComponent(); + } +} + +public enum CheckStatus +{ + Error, + Warning, + Success, +} diff --git a/components/Primitives/samples/SwitchPresenter/SwitchPresenterLayoutSample.xaml.cs b/components/Primitives/samples/SwitchPresenter/SwitchPresenterLayoutSample.xaml.cs index e5edd4e6..e6067234 100644 --- a/components/Primitives/samples/SwitchPresenter/SwitchPresenterLayoutSample.xaml.cs +++ b/components/Primitives/samples/SwitchPresenter/SwitchPresenterLayoutSample.xaml.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using PrimitivesExperiment.Samples.ConstrainedBox; - namespace PrimitivesExperiment.Samples.SwitchPresenter; [ToolkitSample(id: nameof(SwitchPresenterLayoutSample), "SwitchPresenter Layout", description: $"A sample for showing how to use a {nameof(SwitchPresenter)} for complex layouts.")] diff --git a/components/Primitives/samples/SwitchPresenter/SwitchPresenterTemplateSample.xaml b/components/Primitives/samples/SwitchPresenter/SwitchPresenterTemplateSample.xaml new file mode 100644 index 00000000..bd1df35c --- /dev/null +++ b/components/Primitives/samples/SwitchPresenter/SwitchPresenterTemplateSample.xaml @@ -0,0 +1,54 @@ + + + + + Confirmation Code + E-ticket number + Mileage Plan number + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/Primitives/samples/SwitchPresenter/SwitchPresenterTemplateSample.xaml.cs b/components/Primitives/samples/SwitchPresenter/SwitchPresenterTemplateSample.xaml.cs new file mode 100644 index 00000000..5858e90d --- /dev/null +++ b/components/Primitives/samples/SwitchPresenter/SwitchPresenterTemplateSample.xaml.cs @@ -0,0 +1,24 @@ +// 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 PrimitivesExperiment.Samples.SwitchPresenter; + +[ToolkitSample(id: nameof(SwitchPresenterTemplateSample), "SwitchPresenter Template", description: $"A sample for showing how to use a {nameof(SwitchPresenter)} with a Content Template.")] +public sealed partial class SwitchPresenterTemplateSample : Page +{ + public SwitchPresenterTemplateSample() + { + this.InitializeComponent(); + } +} + +public partial class TemplateInformation +{ + public string? Header { get; set; } + + public string? Regex { get; set; } + + public string? PlaceholderText { get; set; } +} + diff --git a/components/Primitives/samples/SwitchPresenter/SwitchPresenterValueSample.xaml b/components/Primitives/samples/SwitchPresenter/SwitchPresenterValueSample.xaml index 807d3d57..9a8b2cf4 100644 --- a/components/Primitives/samples/SwitchPresenter/SwitchPresenterValueSample.xaml +++ b/components/Primitives/samples/SwitchPresenter/SwitchPresenterValueSample.xaml @@ -22,7 +22,11 @@ SelectedIndex="0" /> + Value="{x:Bind AnimalPicker.SelectedItem, Mode=OneWay}"> + + + @@ -31,14 +35,22 @@ - + + Text="🦒" /> + + + + + + diff --git a/components/Primitives/samples/SwitchPresenter/SwitchPresenterValueSample.xaml.cs b/components/Primitives/samples/SwitchPresenter/SwitchPresenterValueSample.xaml.cs index 0fb2f77e..f08a62de 100644 --- a/components/Primitives/samples/SwitchPresenter/SwitchPresenterValueSample.xaml.cs +++ b/components/Primitives/samples/SwitchPresenter/SwitchPresenterValueSample.xaml.cs @@ -15,10 +15,13 @@ public SwitchPresenterValueSample() public enum Animal { + Bunny, Cat, Dog, - Bunny, + Giraffe, Llama, + Otter, + Owl, Parrot, Squirrel } diff --git a/components/Primitives/src/CommunityToolkit.WinUI.Controls.Primitives.csproj b/components/Primitives/src/CommunityToolkit.WinUI.Controls.Primitives.csproj index 18c64107..2dbc8983 100644 --- a/components/Primitives/src/CommunityToolkit.WinUI.Controls.Primitives.csproj +++ b/components/Primitives/src/CommunityToolkit.WinUI.Controls.Primitives.csproj @@ -8,6 +8,7 @@ CommunityToolkit.WinUI.Controls.PrimitivesRns ReadMe.md + true diff --git a/components/Primitives/src/StaggeredLayout/StaggeredColumnLayout.cs b/components/Primitives/src/StaggeredLayout/StaggeredColumnLayout.cs index 89191aa4..49d6d157 100644 --- a/components/Primitives/src/StaggeredLayout/StaggeredColumnLayout.cs +++ b/components/Primitives/src/StaggeredLayout/StaggeredColumnLayout.cs @@ -5,7 +5,7 @@ namespace CommunityToolkit.WinUI.Controls; [DebuggerDisplay("Count = {Count}, Height = {Height}")] -internal class StaggeredColumnLayout : List +internal partial class StaggeredColumnLayout : List { public double Height { get; private set; } diff --git a/components/Primitives/src/StaggeredLayout/StaggeredLayout.cs b/components/Primitives/src/StaggeredLayout/StaggeredLayout.cs index 9e35fa36..28daed13 100644 --- a/components/Primitives/src/StaggeredLayout/StaggeredLayout.cs +++ b/components/Primitives/src/StaggeredLayout/StaggeredLayout.cs @@ -10,7 +10,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// Arranges child elements into a staggered grid pattern where items are added to the column that has used least amount of space. /// -public class StaggeredLayout : VirtualizingLayout +public partial class StaggeredLayout : VirtualizingLayout { /// /// Initializes a new instance of the class. @@ -147,7 +147,7 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size return new Size(availableSize.Width, 0); } - if ((context.RealizationRect.Width == 0) && (context.RealizationRect.Height == 0)) + if (context.RealizationRect is { Width: 0, Height: 0 }) { return new Size(availableSize.Width, 0.0f); } @@ -163,7 +163,7 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size if (ItemsStretch is StaggeredLayoutItemsStretch.None) { columnWidth = double.IsNaN(DesiredColumnWidth) ? availableWidth : Math.Min(DesiredColumnWidth, availableWidth); - numColumns = Math.Max(1, (int)Math.Floor(availableWidth / state.ColumnWidth)); + numColumns = Math.Max(1, (int)Math.Floor(availableWidth / (columnWidth + ColumnSpacing))); } else { @@ -174,13 +174,14 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size } else { - var tempAvailableWidth = availableWidth + ColumnSpacing; - numColumns = (int)Math.Floor(tempAvailableWidth / DesiredColumnWidth); + // 0.0001 is to prevent floating point errors + var tempAvailableWidth = availableWidth + ColumnSpacing - 0.0001; + numColumns = (int)Math.Floor(tempAvailableWidth / (DesiredColumnWidth + ColumnSpacing)); columnWidth = tempAvailableWidth / numColumns - ColumnSpacing; } } - if (columnWidth != state.ColumnWidth) + if (Math.Abs(columnWidth - state.ColumnWidth) > double.Epsilon) { // The items will need to be remeasured state.Clear(); @@ -205,10 +206,10 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size state.ClearColumns(); } - if (RowSpacing != state.RowSpacing) + if (Math.Abs(this.RowSpacing - state.RowSpacing) > double.Epsilon) { // If the RowSpacing changes the height of the rows will be different. - // The columns stores the height so we'll want to clear them out to + // The columns store the height so we'll want to clear them out to // get the proper height state.ClearColumns(); state.RowSpacing = RowSpacing; @@ -228,7 +229,7 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size { // Item has not been measured yet. Get the element and store the values item.Element = context.GetOrCreateElementAt(i); - item.Element.Measure(new Size((float)state.ColumnWidth, (float)availableHeight)); + item.Element.Measure(new Size(state.ColumnWidth, availableHeight)); item.Height = item.Element.DesiredSize.Height; measured = true; } @@ -260,12 +261,12 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size deadColumns.Add(columnIndex); } - else if (measured == false) + else if (!measured) { // We ALWAYS want to measure an item that will be in the bounds item.Element = context.GetOrCreateElementAt(i); - item.Element.Measure(new Size((float)state.ColumnWidth, (float)availableHeight)); - if (item.Height != item.Element.DesiredSize.Height) + item.Element.Measure(new Size(state.ColumnWidth, availableHeight)); + if (Math.Abs(item.Height - item.Element.DesiredSize.Height) > double.Epsilon) { // this item changed size; we need to recalculate layout for everything after this state.RemoveFromIndex(i + 1); @@ -282,7 +283,7 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size double desiredHeight = state.GetHeight(); - return new Size((float)availableWidth, (float)desiredHeight); + return new Size(availableWidth, desiredHeight); } /// diff --git a/components/Primitives/src/StaggeredLayout/StaggeredLayoutState.cs b/components/Primitives/src/StaggeredLayout/StaggeredLayoutState.cs index 10b567a4..4ef319e0 100644 --- a/components/Primitives/src/StaggeredLayout/StaggeredLayoutState.cs +++ b/components/Primitives/src/StaggeredLayout/StaggeredLayoutState.cs @@ -91,14 +91,14 @@ internal void ClearColumns() /// /// The estimated height of the layout. /// - /// If all of the items have been calculated then the actual height will be returned. - /// If all of the items have not been calculated then an estimated height will be calculated based on the average height of the items. + /// If all the items have been calculated then the actual height will be returned. + /// If all the items have not been calculated then an estimated height will be calculated based on the average height of the items. /// internal double GetHeight() { - double desiredHeight = Enumerable.Max(_columnLayout.Values, c => c.Height); + double desiredHeight = _columnLayout.Values.Max(c => c.Height); - var itemCount = Enumerable.Sum(_columnLayout.Values, c => c.Count); + var itemCount = _columnLayout.Values.Sum(c => c.Count); if (itemCount == _context.ItemCount) { return desiredHeight; diff --git a/components/Primitives/src/SwitchPresenter/Case.cs b/components/Primitives/src/SwitchPresenter/Case.cs index d3551f1f..0e95634a 100644 --- a/components/Primitives/src/SwitchPresenter/Case.cs +++ b/components/Primitives/src/SwitchPresenter/Case.cs @@ -13,9 +13,9 @@ public partial class Case : DependencyObject /// /// Gets or sets the Content to display when this case is active. /// - public UIElement Content + public object Content { - get { return (UIElement)GetValue(ContentProperty); } + get { return (object)GetValue(ContentProperty); } set { SetValue(ContentProperty, value); } } @@ -23,7 +23,7 @@ public UIElement Content /// Identifies the property. /// public static readonly DependencyProperty ContentProperty = - DependencyProperty.Register(nameof(Content), typeof(UIElement), typeof(Case), new PropertyMetadata(null)); + DependencyProperty.Register(nameof(Content), typeof(object), typeof(Case), new PropertyMetadata(null)); /// /// Gets or sets a value indicating whether this is the default case to display when no values match the specified value in the . There should only be a single default case provided. Do not set the property when setting to true. Default is false. diff --git a/components/Primitives/src/SwitchPresenter/CaseCollection.cs b/components/Primitives/src/SwitchPresenter/CaseCollection.cs index a0ad7777..2eab6497 100644 --- a/components/Primitives/src/SwitchPresenter/CaseCollection.cs +++ b/components/Primitives/src/SwitchPresenter/CaseCollection.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// An collection of to help with XAML interop. /// -public class CaseCollection : DependencyObjectCollection +public partial class CaseCollection : DependencyObjectCollection { /// /// Initializes a new instance of the class. diff --git a/components/Primitives/src/SwitchPresenter/SwitchConverter.cs b/components/Primitives/src/SwitchPresenter/SwitchConverter.cs new file mode 100644 index 00000000..7ce8bad2 --- /dev/null +++ b/components/Primitives/src/SwitchPresenter/SwitchConverter.cs @@ -0,0 +1,80 @@ +// 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 CommunityToolkit.WinUI.Converters; + +/// +/// A helper which can automatically translate incoming data to a set of resulting values defined in XAML. +/// +/// +/// <converters:SwitchConverter x:Key="StatusToColorSwitchConverter" +/// TargetType="models:CheckStatus"> +/// <controls:Case Value="Error" Content="{ThemeResource SystemFillColorCriticalBrush}"/> +/// <controls:Case Value="Warning" Content="{ThemeResource SystemFillColorCautionBrush}"/> +/// <controls:Case Value="Success" Content="{ThemeResource SystemFillColorSuccessBrush}"/> +/// </converters:SwitchConverter> +/// ... +/// <TextBlock +/// FontWeight="SemiBold" +/// Foreground="{x:Bind Status, Converter={StaticResource StatusToColorSwitchConverter}}" +/// Text = "{x:Bind Status}" /> +/// +[ContentProperty(Name = nameof(SwitchCases))] +public sealed partial class SwitchConverter : DependencyObject, IValueConverter +{ + /// + /// Gets or sets a value representing the collection of cases to evaluate. + /// + public CaseCollection SwitchCases + { + get { return (CaseCollection)GetValue(SwitchCasesProperty); } + set { SetValue(SwitchCasesProperty, value); } + } + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty SwitchCasesProperty = + DependencyProperty.Register(nameof(SwitchCases), typeof(CaseCollection), typeof(SwitchConverter), new PropertyMetadata(null)); + + /// + /// Gets or sets a value indicating which type to first cast and compare provided values against. + /// + public Type TargetType + { + get { return (Type)GetValue(TargetTypeProperty); } + set { SetValue(TargetTypeProperty, value); } + } + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty TargetTypeProperty = + DependencyProperty.Register(nameof(TargetType), typeof(Type), typeof(SwitchConverter), new PropertyMetadata(null)); + + /// + /// Initializes a new instance of the class. + /// + public SwitchConverter() + { + // Note: we need to initialize this here so that XAML can automatically add cases without needing this defined around it as the content. + // We don't do this in the PropertyMetadata as then we create a static shared collection for all converters, which we don't want. We want it per instance. + // See https://learn.microsoft.com/windows/uwp/xaml-platform/custom-dependency-properties#initializing-the-collection + SwitchCases = new CaseCollection(); + } + + public object Convert(object value, Type targetType, object parameter, string language) + { + var result = SwitchCases.EvaluateCases(value, TargetType ?? targetType); + + return result?.Content!; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} diff --git a/components/Primitives/src/SwitchPresenter/SwitchHelpers.cs b/components/Primitives/src/SwitchPresenter/SwitchHelpers.cs new file mode 100644 index 00000000..c3698931 --- /dev/null +++ b/components/Primitives/src/SwitchPresenter/SwitchHelpers.cs @@ -0,0 +1,135 @@ +// 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.Converters; + +namespace CommunityToolkit.WinUI.Controls; + +/// +/// Internal helpers for use between and . +/// The logic here is the main code which looks across a to match a specific with a given value while converting types based on the property. This will handle values as well as values compatible with the method. +/// +internal static partial class SwitchHelpers +{ + /// + /// Extension method for a set of cases to find the matching case given its value and type. + /// + /// The collection of s in a + /// The value of the to find + /// The desired type of the result for automatic conversion + /// The discovered value, the default value, or null + internal static Case? EvaluateCases(this CaseCollection switchCases, object value, Type targetType) + { + if (switchCases == null || + switchCases.Count == 0) + { + // If we have no cases, then we can't match anything. + return null; + } + + Case? xdefault = null; + Case? newcase = null; + + foreach (Case xcase in switchCases) + { + if (xcase.IsDefault) + { + // If there are multiple default cases provided, this will override and just grab the last one, the developer will have to fix this in their XAML. We call this out in the case comments. + xdefault = xcase; + continue; + } + + if (CompareValues(value, xcase.Value, targetType)) + { + newcase = xcase; + break; + } + } + + if (newcase == null && xdefault != null) + { + // Inject default if we found one without matching anything + newcase = xdefault; + } + + return newcase; + } + + /// + /// Compares two values using the TargetType. + /// + /// Our main value in our SwitchPresenter. + /// The value from the case to compare to. + /// true if the two values are equal + internal static bool CompareValues(object compare, object value, Type targetType) + { + if (compare == null || value == null) + { + return compare == value; + } + + if (targetType == null || + (targetType == compare.GetType() && + targetType == value.GetType())) + { + // Default direct object comparison or we're all the proper type + return compare.Equals(value); + } + else if (compare.GetType() == targetType) + { + // If we have a TargetType and the first value is the right type + // Then our 2nd value isn't, so convert to string and coerce. + var valueBase2 = ConvertValue(targetType, value); + + return compare.Equals(valueBase2); + } + + // Neither of our two values matches the type so + // we'll convert both to a String and try and coerce it to the proper type. + var compareBase = ConvertValue(targetType, compare); + + var valueBase = ConvertValue(targetType, value); + + return compareBase.Equals(valueBase); + } + + /// + /// Helper method to convert a value from a source type to a target type. + /// + /// The target type + /// The value to convert + /// The converted value + internal static object ConvertValue(Type targetType, object value) + { + if (targetType.IsInstanceOfType(value)) + { + return value; + } + else if (targetType.IsEnum && value is string str) + { +#if HAS_UNO + if (Enum.IsDefined(targetType, str)) + { + return Enum.Parse(targetType, str); + } +#else + if (Enum.TryParse(targetType, str, out object? result)) + { + return result!; + } +#endif + + static object ThrowExceptionForKeyNotFound() + { + throw new InvalidOperationException("The requested enum value was not present in the provided type."); + } + + return ThrowExceptionForKeyNotFound(); + } + else + { + return XamlBindingHelper.ConvertValue(targetType, value); + } + } +} diff --git a/components/Primitives/src/SwitchPresenter/SwitchPresenter.cs b/components/Primitives/src/SwitchPresenter/SwitchPresenter.cs index 870da7dc..9ebc6b6e 100644 --- a/components/Primitives/src/SwitchPresenter/SwitchPresenter.cs +++ b/components/Primitives/src/SwitchPresenter/SwitchPresenter.cs @@ -115,20 +115,7 @@ protected override void OnApplyTemplate() private void EvaluateCases() { - if (SwitchCases == null || - SwitchCases.Count == 0) - { - // If we have no cases, then we can't match anything. - if (CurrentCase != null) - { - // Only bother clearing our actual content if we had something before. - Content = null; - CurrentCase = null; - } - - return; - } - else if (CurrentCase?.Value != null && + if (CurrentCase?.Value != null && CurrentCase.Value.Equals(Value)) { // If the current case we're on already matches our current value, @@ -136,114 +123,14 @@ private void EvaluateCases() return; } - Case? xdefault = null; - Case? newcase = null; - - foreach (Case xcase in SwitchCases) - { - if (xcase.IsDefault) - { - // If there are multiple default cases provided, this will override and just grab the last one, the developer will have to fix this in their XAML. We call this out in the case comments. - xdefault = xcase; - continue; - } - - if (CompareValues(Value, xcase.Value)) - { - newcase = xcase; - break; - } - } - - if (newcase == null && xdefault != null) - { - // Inject default if we found one without matching anything - newcase = xdefault; - } + var result = SwitchCases.EvaluateCases(Value, TargetType); - // Only bother changing things around if we actually have a new case. - if (newcase != CurrentCase) + // Only bother changing things around if we actually have a new case. (this should handle prior null case as well) + if (result != CurrentCase) { // If we don't have any cases or default, setting these to null is what we want to be blank again. - Content = newcase?.Content; - CurrentCase = newcase; - } - } - - /// - /// Compares two values using the TargetType. - /// - /// Our main value in our SwitchPresenter. - /// The value from the case to compare to. - /// true if the two values are equal - private bool CompareValues(object compare, object value) - { - if (compare == null || value == null) - { - return compare == value; - } - - if (TargetType == null || - (TargetType == compare.GetType() && - TargetType == value.GetType())) - { - // Default direct object comparison or we're all the proper type - return compare.Equals(value); - } - else if (compare.GetType() == TargetType) - { - // If we have a TargetType and the first value is the right type - // Then our 2nd value isn't, so convert to string and coerce. - var valueBase2 = ConvertValue(TargetType, value); - - return compare.Equals(valueBase2); - } - - // Neither of our two values matches the type so - // we'll convert both to a String and try and coerce it to the proper type. - var compareBase = ConvertValue(TargetType, compare); - - var valueBase = ConvertValue(TargetType, value); - - return compareBase.Equals(valueBase); - } - - /// - /// Helper method to convert a value from a source type to a target type. - /// - /// The target type - /// The value to convert - /// The converted value - internal static object ConvertValue(Type targetType, object value) - { - if (targetType.IsInstanceOfType(value)) - { - return value; - } - else if (targetType.IsEnum && value is string str) - { -#if HAS_UNO - if (Enum.IsDefined(targetType, str)) - { - return Enum.Parse(targetType, str); - } -#else - if (Enum.TryParse(targetType, str, out object? result)) - { - return result!; - } -#endif - - static object ThrowExceptionForKeyNotFound() - { - throw new InvalidOperationException("The requested enum value was not present in the provided type."); - } - - return ThrowExceptionForKeyNotFound(); - } - else - { - return XamlBindingHelper.ConvertValue(targetType, value); + Content = result?.Content; + CurrentCase = result; } } } diff --git a/components/Primitives/src/WrapLayout/WrapLayout.cs b/components/Primitives/src/WrapLayout/WrapLayout.cs index 7bc99a0f..cb3f9462 100644 --- a/components/Primitives/src/WrapLayout/WrapLayout.cs +++ b/components/Primitives/src/WrapLayout/WrapLayout.cs @@ -12,7 +12,7 @@ namespace CommunityToolkit.WinUI.Controls; /// When is set to Orientation.Horizontal, element are arranged in rows until the available width is reached and then to a new row. /// When is set to Orientation.Vertical, element are arranged in columns until the available height is reached. /// -public class WrapLayout : VirtualizingLayout +public partial class WrapLayout : VirtualizingLayout { /// /// Gets or sets a uniform Horizontal distance (in pixels) between items when is set to Horizontal, diff --git a/components/Primitives/tests/Primitives.Tests.projitems b/components/Primitives/tests/Primitives.Tests.projitems index 344de9e5..efdf851b 100644 --- a/components/Primitives/tests/Primitives.Tests.projitems +++ b/components/Primitives/tests/Primitives.Tests.projitems @@ -9,6 +9,13 @@ PrimitivesExperiment.Tests + + %(Filename) + + + %(Filename) + + @@ -33,4 +40,14 @@ MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + \ No newline at end of file diff --git a/components/Primitives/tests/SwitchPresenter/SwitchConverterBrushSample.xaml b/components/Primitives/tests/SwitchPresenter/SwitchConverterBrushSample.xaml new file mode 100644 index 00000000..9d6820e3 --- /dev/null +++ b/components/Primitives/tests/SwitchPresenter/SwitchConverterBrushSample.xaml @@ -0,0 +1,53 @@ + + + + + Warning + + + + + + + + + + + + + + + + + + + Success + Warning + Error + + + + diff --git a/components/Primitives/tests/SwitchPresenter/SwitchConverterBrushSample.xaml.cs b/components/Primitives/tests/SwitchPresenter/SwitchConverterBrushSample.xaml.cs new file mode 100644 index 00000000..d5319277 --- /dev/null +++ b/components/Primitives/tests/SwitchPresenter/SwitchConverterBrushSample.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. + +namespace PrimitivesExperiment.Tests; + +public sealed partial class SwitchConverterBrushSample : Page +{ + public SwitchConverterBrushSample() + { + this.InitializeComponent(); + } +} + +public enum CheckStatus +{ + Error, + Warning, + Success, +} diff --git a/components/Primitives/tests/SwitchPresenter/SwitchPresenterLayoutSample.xaml b/components/Primitives/tests/SwitchPresenter/SwitchPresenterLayoutSample.xaml new file mode 100644 index 00000000..6465b41b --- /dev/null +++ b/components/Primitives/tests/SwitchPresenter/SwitchPresenterLayoutSample.xaml @@ -0,0 +1,54 @@ + + + + + Select an option + Confirmation Code + E-ticket number + Mileage Plan number + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/Primitives/tests/SwitchPresenter/SwitchPresenterLayoutSample.xaml.cs b/components/Primitives/tests/SwitchPresenter/SwitchPresenterLayoutSample.xaml.cs new file mode 100644 index 00000000..d6cbcb1b --- /dev/null +++ b/components/Primitives/tests/SwitchPresenter/SwitchPresenterLayoutSample.xaml.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. + +namespace PrimitivesExperiment.Tests; + +public sealed partial class SwitchPresenterLayoutSample : Page +{ + public SwitchPresenterLayoutSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Primitives/tests/SwitchPresenter/SwitchPresenterTests.cs b/components/Primitives/tests/SwitchPresenter/SwitchPresenterTests.cs new file mode 100644 index 00000000..63b5862a --- /dev/null +++ b/components/Primitives/tests/SwitchPresenter/SwitchPresenterTests.cs @@ -0,0 +1,142 @@ +// 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.Tests; +using CommunityToolkit.Tooling.TestGen; +using CommunityToolkit.WinUI.Controls; +using CommunityToolkit.WinUI.Converters; + +namespace PrimitivesExperiment.Tests; + +[TestClass] +public partial class SwitchPresenterTests : VisualUITestBase +{ + [UIThreadTestMethod] + public async Task SwitchPresenterLayoutTest(SwitchPresenterLayoutSample page) + { + var spresenter = page.FindDescendant(); + var combobox = page.FindDescendant(); + + Assert.IsNotNull(spresenter, "Couldn't find SwitchPresenter"); + Assert.IsNotNull(combobox, "Couldn't find ComboBox"); + + var dcase = spresenter.SwitchCases.OfType().FirstOrDefault(@case => @case.IsDefault); + + Assert.IsNotNull(dcase, "Couldn't find default case"); + + // Are we in our initial case? + Assert.AreEqual("Select an option", spresenter.Value, "SwitchPresenter not expected starting value"); + Assert.AreEqual(dcase, spresenter.CurrentCase, "SwitchPresenter not at current default case"); + + // Can we find our matching textbox? + var tbox = spresenter.FindDescendant(); + Assert.IsNotNull(tbox, "Couldn't find inner textbox"); + Assert.AreEqual("Please select a way to lookup your reservation above...", tbox.Text, "Unexpected content for SwitchPresenter default case"); + + // Update combobox + combobox.SelectedIndex = 1; + + // Wait for update + await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { }); + + // Are we in the new case? + Assert.AreEqual("Confirmation Code", spresenter.Value); + + var ccase = spresenter.SwitchCases.OfType().FirstOrDefault(@case => @case.Value?.ToString() == "Confirmation Code"); + + Assert.IsNotNull(ccase, "Couldn't find expected case"); + + Assert.AreEqual(ccase, spresenter.CurrentCase, "SwitchPresenter didn't change cases"); + + var txtbox = spresenter.FindDescendant(); + Assert.IsNotNull(txtbox, "Couldn't find new textbox"); + Assert.AreEqual("Confirmation code", txtbox.Header, "Textbox header not expected value"); + } + + [UIThreadTestMethod] + public async Task SwitchConverterBrushTest(SwitchConverterBrushSample page) + { + var combobox = page.FindDescendant(); + var textblock = page.FindDescendant("ResultBlock") as TextBlock; + + Assert.IsNotNull(combobox, "Couldn't find ComboBox"); + Assert.IsNotNull(textblock, "Couldn't find SwitchPresenter"); + + // Are we in our initial case? + Assert.AreEqual(0, combobox.SelectedIndex, "ComboBox not initialized"); + Assert.AreEqual("Success", combobox.SelectedItem, "Not expected starting value in ComboBox"); + + // Check the TextBlock's brush + Assert.AreEqual(((SolidColorBrush)page.Resources["SystemFillColorSuccessBrush"]).Color, ((SolidColorBrush)textblock.Foreground).Color, "TextBlock not in initial success state"); + + // Update combobox + combobox.SelectedIndex = 1; + + // Wait for update + await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { }); + + // Are we in the new case? + Assert.AreEqual("Warning", combobox.SelectedItem, "ComboBox didn't change"); + + // Check the TextBlock's brush + Assert.AreEqual(((SolidColorBrush)page.Resources["SystemFillColorCautionBrush"]).Color, ((SolidColorBrush)textblock.Foreground).Color, "TextBlock not in new state"); + + // Update combobox + combobox.SelectedIndex = 2; + + // Wait for update + await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { }); + + // Are we in the new case? + Assert.AreEqual("Error", combobox.SelectedItem, "ComboBox didn't change 2"); + + // Check the TextBlock's brush + Assert.AreEqual(((SolidColorBrush)page.Resources["SystemFillColorCriticalBrush"]).Color, ((SolidColorBrush)textblock.Foreground).Color, "TextBlock not in final state"); + } + + [UIThreadTestMethod] + public void SwitchConverterDirectTest() + { + // Multiply by 10 + SwitchConverter sconverter = new() + { + SwitchCases = new CaseCollection { + new Case() + { + Content = 10, + Value = 1, + }, + new Case() + { + Content = 50, + Value = 5, + IsDefault = true + }, + new Case() + { + Content = 30, + Value = 3, + }, + new Case() + { + Content = 20, + Value = 2, + }, + }, + TargetType = typeof(int) + }; + + Assert.IsNotNull(sconverter); + Assert.AreEqual(4, sconverter.SwitchCases.Count, "Not 4 cases"); + + var @default = sconverter.Convert(100, typeof(int), string.Empty, string.Empty); + + Assert.AreEqual(50, @default, "Unexpected default return"); + + Assert.AreEqual(10, sconverter.Convert(1, typeof(int), string.Empty, string.Empty), "Unexpected result with 1"); + Assert.AreEqual(20, sconverter.Convert(2, typeof(int), string.Empty, string.Empty), "Unexpected result with 2"); + Assert.AreEqual(30, sconverter.Convert(3, typeof(int), string.Empty, string.Empty), "Unexpected result with 3"); + Assert.AreEqual(50, sconverter.Convert(5, typeof(int), string.Empty, string.Empty), "Unexpected result with 5"); + } +} diff --git a/components/RadialGauge/src/RadialGaugeAutomationPeer.cs b/components/RadialGauge/src/RadialGaugeAutomationPeer.cs index 2550a8ca..cc8400d3 100644 --- a/components/RadialGauge/src/RadialGaugeAutomationPeer.cs +++ b/components/RadialGauge/src/RadialGaugeAutomationPeer.cs @@ -13,7 +13,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// Exposes to Microsoft UI Automation. /// -public class RadialGaugeAutomationPeer : +public partial class RadialGaugeAutomationPeer : RangeBaseAutomationPeer, IRangeValueProvider { diff --git a/components/RangeSelector/src/RangeSelector.Input.Drag.cs b/components/RangeSelector/src/RangeSelector.Input.Drag.cs index 6112fabc..e8a8fd6a 100644 --- a/components/RangeSelector/src/RangeSelector.Input.Drag.cs +++ b/components/RangeSelector/src/RangeSelector.Input.Drag.cs @@ -77,7 +77,7 @@ private double DragThumb(Thumb? thumb, double min, double max, double nextPos) Canvas.SetLeft(thumb, nextPos); - if (_toolTipText != null && _toolTip != null && thumb != null) + if (_toolTip != null && thumb != null) { var thumbCenter = nextPos + (thumb.Width / 2); _toolTip.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); @@ -99,7 +99,7 @@ private void Thumb_DragStarted(Thumb thumb) Canvas.SetZIndex(otherThumb, 0); _oldValue = RangeStart; - if (_toolTipText != null && _toolTip != null) + if (_toolTip != null) { _toolTip.Visibility = Visibility.Visible; var thumbCenter = _absolutePosition + (thumb.Width / 2); @@ -107,7 +107,8 @@ private void Thumb_DragStarted(Thumb thumb) var ttWidth = _toolTip.ActualWidth / 2; Canvas.SetLeft(_toolTip, thumbCenter - ttWidth); - UpdateToolTipText(this, _toolTipText, useMin ? RangeStart : RangeEnd); + if (_toolTipText != null) + UpdateToolTipText(this, _toolTipText, useMin ? RangeStart : RangeEnd); } VisualStateManager.GoToState(this, useMin ? MinPressedState : MaxPressedState, true); diff --git a/components/RichSuggestBox/samples/SuggestionTemplateSelector.cs b/components/RichSuggestBox/samples/SuggestionTemplateSelector.cs index 2e4089b7..ac1fbf2d 100644 --- a/components/RichSuggestBox/samples/SuggestionTemplateSelector.cs +++ b/components/RichSuggestBox/samples/SuggestionTemplateSelector.cs @@ -4,7 +4,7 @@ namespace RichSuggestBoxExperiment.Samples; -public class SuggestionTemplateSelector : DataTemplateSelector +public partial class SuggestionTemplateSelector : DataTemplateSelector { public DataTemplate? Person { get; set; } diff --git a/components/RichSuggestBox/src/CommunityToolkit.WinUI.Controls.RichSuggestBox.csproj b/components/RichSuggestBox/src/CommunityToolkit.WinUI.Controls.RichSuggestBox.csproj index f50bf0ff..b3e2f0d2 100644 --- a/components/RichSuggestBox/src/CommunityToolkit.WinUI.Controls.RichSuggestBox.csproj +++ b/components/RichSuggestBox/src/CommunityToolkit.WinUI.Controls.RichSuggestBox.csproj @@ -8,6 +8,7 @@ CommunityToolkit.WinUI.Controls.RichSuggestBoxRns ReadMe.md + true diff --git a/components/RichSuggestBox/src/RichSuggestToken.cs b/components/RichSuggestBox/src/RichSuggestToken.cs index 5a66f6eb..69443f33 100644 --- a/components/RichSuggestBox/src/RichSuggestToken.cs +++ b/components/RichSuggestBox/src/RichSuggestToken.cs @@ -13,7 +13,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// RichSuggestToken describes a suggestion token in the document. /// -public class RichSuggestToken : INotifyPropertyChanged +public partial class RichSuggestToken : INotifyPropertyChanged { /// public event PropertyChangedEventHandler? PropertyChanged; diff --git a/components/SettingsControls/samples/SettingsExpanderItemsSourceSample.xaml.cs b/components/SettingsControls/samples/SettingsExpanderItemsSourceSample.xaml.cs index 80a68344..28d00375 100644 --- a/components/SettingsControls/samples/SettingsExpanderItemsSourceSample.xaml.cs +++ b/components/SettingsControls/samples/SettingsExpanderItemsSourceSample.xaml.cs @@ -61,7 +61,7 @@ public class MyDataModel public string? Url { get; set; } } -public class MyDataModelTemplateSelector : DataTemplateSelector +public partial class MyDataModelTemplateSelector : DataTemplateSelector { public DataTemplate? ButtonTemplate { get; set; } public DataTemplate? LinkButtonTemplate { get; set; } diff --git a/components/SettingsControls/src/CommunityToolkit.WinUI.Controls.SettingsControls.csproj b/components/SettingsControls/src/CommunityToolkit.WinUI.Controls.SettingsControls.csproj index 86b9a3a9..c605d7ca 100644 --- a/components/SettingsControls/src/CommunityToolkit.WinUI.Controls.SettingsControls.csproj +++ b/components/SettingsControls/src/CommunityToolkit.WinUI.Controls.SettingsControls.csproj @@ -8,6 +8,7 @@ CommunityToolkit.WinUI.Controls.SettingsControlsRns ReadMe.md + true diff --git a/components/SettingsControls/src/Helpers/CornerRadiusConverter.cs b/components/SettingsControls/src/Helpers/CornerRadiusConverter.cs index 5a1a5737..d833b4b8 100644 --- a/components/SettingsControls/src/Helpers/CornerRadiusConverter.cs +++ b/components/SettingsControls/src/Helpers/CornerRadiusConverter.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. namespace CommunityToolkit.WinUI.Controls; -internal class CornerRadiusConverter : IValueConverter +internal partial class CornerRadiusConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, string language) { diff --git a/components/SettingsControls/src/Helpers/StyleExtensions.cs b/components/SettingsControls/src/Helpers/StyleExtensions.cs index 830799f7..2a44d465 100644 --- a/components/SettingsControls/src/Helpers/StyleExtensions.cs +++ b/components/SettingsControls/src/Helpers/StyleExtensions.cs @@ -8,7 +8,7 @@ namespace CommunityToolkit.WinUI.Controls; public static partial class StyleExtensions { // Used to distinct normal ResourceDictionary and the one we add. - private sealed class StyleExtensionResourceDictionary : ResourceDictionary + private sealed partial class StyleExtensionResourceDictionary : ResourceDictionary { } diff --git a/components/SettingsControls/src/SettingsCard/SettingsCardAutomationPeer.cs b/components/SettingsControls/src/SettingsCard/SettingsCardAutomationPeer.cs index dc049ca0..6d287812 100644 --- a/components/SettingsControls/src/SettingsCard/SettingsCardAutomationPeer.cs +++ b/components/SettingsControls/src/SettingsCard/SettingsCardAutomationPeer.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// AutomationPeer for SettingsCard /// -public class SettingsCardAutomationPeer : FrameworkElementAutomationPeer +public partial class SettingsCardAutomationPeer : FrameworkElementAutomationPeer { /// /// Initializes a new instance of the class. diff --git a/components/SettingsControls/src/SettingsExpander/SettingsExpander.Properties.cs b/components/SettingsControls/src/SettingsExpander/SettingsExpander.Properties.cs index b209f16f..d3924795 100644 --- a/components/SettingsControls/src/SettingsExpander/SettingsExpander.Properties.cs +++ b/components/SettingsControls/src/SettingsExpander/SettingsExpander.Properties.cs @@ -45,7 +45,7 @@ public partial class SettingsExpander new PropertyMetadata(defaultValue: null)); /// - /// The backing for the property. + /// The backing for the property. /// public static readonly DependencyProperty ItemsHeaderProperty = DependencyProperty.Register( nameof(ItemsHeader), @@ -54,7 +54,7 @@ public partial class SettingsExpander new PropertyMetadata(defaultValue: null)); /// - /// The backing for the property. + /// The backing for the property. /// public static readonly DependencyProperty ItemsFooterProperty = DependencyProperty.Register( nameof(ItemsFooter), diff --git a/components/SettingsControls/src/SettingsExpander/SettingsExpanderAutomationPeer.cs b/components/SettingsControls/src/SettingsExpander/SettingsExpanderAutomationPeer.cs index d6cb35a6..0fef3e63 100644 --- a/components/SettingsControls/src/SettingsExpander/SettingsExpanderAutomationPeer.cs +++ b/components/SettingsControls/src/SettingsExpander/SettingsExpanderAutomationPeer.cs @@ -12,7 +12,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// AutomationPeer for SettingsExpander /// -public class SettingsExpanderAutomationPeer : FrameworkElementAutomationPeer +public partial class SettingsExpanderAutomationPeer : FrameworkElementAutomationPeer { /// /// Initializes a new instance of the class. diff --git a/components/SettingsControls/src/SettingsExpander/SettingsExpanderItemStyleSelector.cs b/components/SettingsControls/src/SettingsExpander/SettingsExpanderItemStyleSelector.cs index f87f57a8..c62e654a 100644 --- a/components/SettingsControls/src/SettingsExpander/SettingsExpanderItemStyleSelector.cs +++ b/components/SettingsControls/src/SettingsExpander/SettingsExpanderItemStyleSelector.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// used by to choose the proper container style (clickable or not). /// -public class SettingsExpanderItemStyleSelector : StyleSelector +public partial class SettingsExpanderItemStyleSelector : StyleSelector { /// /// Gets or sets the default . diff --git a/components/Sizers/src/SizerAutomationPeer.cs b/components/Sizers/src/SizerAutomationPeer.cs index 16462c7f..93931a55 100644 --- a/components/Sizers/src/SizerAutomationPeer.cs +++ b/components/Sizers/src/SizerAutomationPeer.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Controls.Automation.Peers; /// /// Defines a framework element automation peer for the controls. /// -public class SizerAutomationPeer : FrameworkElementAutomationPeer +public partial class SizerAutomationPeer : FrameworkElementAutomationPeer { /// /// Initializes a new instance of the class. diff --git a/components/TabbedCommandBar/src/TabbedCommandBarItemTemplateSelector.cs b/components/TabbedCommandBar/src/TabbedCommandBarItemTemplateSelector.cs index bea08963..5197d742 100644 --- a/components/TabbedCommandBar/src/TabbedCommandBarItemTemplateSelector.cs +++ b/components/TabbedCommandBar/src/TabbedCommandBarItemTemplateSelector.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// used by for determining the style of normal vs. contextual s. /// -public class TabbedCommandBarItemTemplateSelector : DataTemplateSelector +public partial class TabbedCommandBarItemTemplateSelector : DataTemplateSelector { /// /// Gets or sets the of a normal . diff --git a/components/TokenizingTextBox/src/CommunityToolkit.WinUI.Controls.TokenizingTextBox.csproj b/components/TokenizingTextBox/src/CommunityToolkit.WinUI.Controls.TokenizingTextBox.csproj index 572d6a05..bc73b6cb 100644 --- a/components/TokenizingTextBox/src/CommunityToolkit.WinUI.Controls.TokenizingTextBox.csproj +++ b/components/TokenizingTextBox/src/CommunityToolkit.WinUI.Controls.TokenizingTextBox.csproj @@ -8,6 +8,7 @@ CommunityToolkit.WinUI.Controls.TokenizingTextBoxRns ReadMe.md + true diff --git a/components/TokenizingTextBox/src/InterspersedObservableCollection.cs b/components/TokenizingTextBox/src/InterspersedObservableCollection.cs index 74a3b81c..c2296ff3 100644 --- a/components/TokenizingTextBox/src/InterspersedObservableCollection.cs +++ b/components/TokenizingTextBox/src/InterspersedObservableCollection.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI.Controls; #pragma warning disable CS8622 #pragma warning disable CS8603 #pragma warning disable CS8714 -internal class InterspersedObservableCollection : IList, IEnumerable, INotifyCollectionChanged +internal partial class InterspersedObservableCollection : IList, IEnumerable, INotifyCollectionChanged { public IList ItemsSource { get; private set; } diff --git a/components/TokenizingTextBox/src/TokenizingTextBoxAutomationPeer.cs b/components/TokenizingTextBox/src/TokenizingTextBoxAutomationPeer.cs index b3c8b7df..ca2f30e4 100644 --- a/components/TokenizingTextBox/src/TokenizingTextBoxAutomationPeer.cs +++ b/components/TokenizingTextBox/src/TokenizingTextBoxAutomationPeer.cs @@ -3,9 +3,11 @@ // See the LICENSE file in the project root for more information. using CommunityToolkit.WinUI.Controls; -#if WINAPPSDK +#if WINUI3 +using Microsoft.UI.Xaml.Automation; using Microsoft.UI.Xaml.Automation.Provider; #else +using Windows.UI.Xaml.Automation; using Windows.UI.Xaml.Automation.Provider; #endif @@ -14,7 +16,7 @@ namespace CommunityToolkit.WinUI.Automation.Peers; /// /// Defines a framework element automation peer for the control. /// -public class TokenizingTextBoxAutomationPeer : ListViewBaseAutomationPeer, IValueProvider +public partial class TokenizingTextBoxAutomationPeer : ListViewBaseAutomationPeer, IValueProvider { /// /// Initializes a new instance of the class. @@ -47,7 +49,7 @@ private TokenizingTextBox OwningTokenizingTextBox /// Sets the value of a control. /// The value to set. The provider is responsible for converting the value to the appropriate data type. - /// Thrown if the control is in a read-only state. + /// Thrown if the control is in a read-only state. public void SetValue(string value) { if (IsReadOnly) diff --git a/components/TokenizingTextBox/src/TokenizingTextBoxStyleSelector.cs b/components/TokenizingTextBox/src/TokenizingTextBoxStyleSelector.cs index 8d75fafa..200ab74c 100644 --- a/components/TokenizingTextBox/src/TokenizingTextBoxStyleSelector.cs +++ b/components/TokenizingTextBox/src/TokenizingTextBoxStyleSelector.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.WinUI.Controls; /// /// used by to choose the proper container style (text entry or token). /// -public class TokenizingTextBoxStyleSelector : StyleSelector +public partial class TokenizingTextBoxStyleSelector : StyleSelector { /// /// Gets or sets the of a token item. diff --git a/components/TokenizingTextBox/tests/Test_TokenizingTextBox_AutomationPeer.cs b/components/TokenizingTextBox/tests/Test_TokenizingTextBox_AutomationPeer.cs index 5b90b3fd..32094d58 100644 --- a/components/TokenizingTextBox/tests/Test_TokenizingTextBox_AutomationPeer.cs +++ b/components/TokenizingTextBox/tests/Test_TokenizingTextBox_AutomationPeer.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using CommunityToolkit.Tests; +using CommunityToolkit.Tooling.TestGen; using CommunityToolkit.WinUI.Automation.Peers; using CommunityToolkit.WinUI.Controls; @@ -10,7 +11,7 @@ namespace TokenizingTextBoxExperiment.Tests; [TestClass] [TestCategory("Test_TokenizingTextBox")] -public class Test_TokenizingTextBox_AutomationPeer : VisualUITestBase +public partial class Test_TokenizingTextBox_AutomationPeer : VisualUITestBase { [TestMethod] public async Task ShouldConfigureTokenizingTextBoxAutomationPeerAsync() @@ -83,24 +84,21 @@ await App.DispatcherQueue.EnqueueAsync(async () => }); } - [TestMethod] + [UIThreadTestMethod] public async Task ShouldThrowElementNotEnabledExceptionIfValueSetWhenDisabled() { - await App.DispatcherQueue.EnqueueAsync(async () => - { - const string expectedValue = "Wor"; + const string expectedValue = "Wor"; - var tokenizingTextBox = new TokenizingTextBox { IsEnabled = false }; + var tokenizingTextBox = new TokenizingTextBox { IsEnabled = false }; - await LoadTestContentAsync(tokenizingTextBox); + await LoadTestContentAsync(tokenizingTextBox); - var tokenizingTextBoxAutomationPeer = - FrameworkElementAutomationPeer.CreatePeerForElement(tokenizingTextBox) as TokenizingTextBoxAutomationPeer; + var tokenizingTextBoxAutomationPeer = + FrameworkElementAutomationPeer.CreatePeerForElement(tokenizingTextBox) as TokenizingTextBoxAutomationPeer; - Assert.ThrowsException(() => - { - tokenizingTextBoxAutomationPeer!.SetValue(expectedValue); - }); + Assert.ThrowsException(() => + { + tokenizingTextBoxAutomationPeer!.SetValue(expectedValue); }); } diff --git a/components/Triggers/src/CompareStateTrigger.cs b/components/Triggers/src/CompareStateTrigger.cs index a44aa6c1..c01c6f1d 100644 --- a/components/Triggers/src/CompareStateTrigger.cs +++ b/components/Triggers/src/CompareStateTrigger.cs @@ -13,7 +13,7 @@ namespace CommunityToolkit.WinUI; /// /// Example: Trigger if a value is greater than 0 /// - /// <triggers:CompareStateTrigger Value="{Binding MyValue}" CompareTo="0" Comparison="GreaterThan" /> + /// <triggers:CompareStateTrigger Value="{x:Bind MyValue, Mode=OneWay}" CompareTo="0" Comparison="GreaterThan" /> /// /// /// diff --git a/components/Triggers/src/IsEqualStateTrigger.cs b/components/Triggers/src/IsEqualStateTrigger.cs index 2282acab..04b6bbd1 100644 --- a/components/Triggers/src/IsEqualStateTrigger.cs +++ b/components/Triggers/src/IsEqualStateTrigger.cs @@ -13,7 +13,7 @@ namespace CommunityToolkit.WinUI; /// /// Example: Trigger if a value is null /// -/// <triggers:EqualsStateTrigger Value="{Binding MyObject}" EqualTo="{x:Null}" /> +/// <triggers:EqualsStateTrigger Value="{x:Bind MyObject, Mode=OneWay}" EqualTo="{x:Null}" /> /// /// /// diff --git a/global.json b/global.json index e4e1a5f4..27a18775 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.201", + "version": "9.0.101", "rollForward": "latestFeature" }, "msbuild-sdks": diff --git a/tooling b/tooling index e366657a..93931e0b 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit e366657a924a745c6fb4162dc4c58d2e07fc0613 +Subproject commit 93931e0beda3520fcc68e8bf9975a4ebb7067674