diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1d6e32e0..3b0db68e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -17,7 +17,7 @@ on:
merge_group:
env:
- DOTNET_VERSION: ${{ '9.0.100' }}
+ DOTNET_VERSION: ${{ '9.0.x' }}
ENABLE_DIAGNOSTICS: true
MSBUILD_VERBOSITY: normal
#COREHOST_TRACE: 1
diff --git a/Directory.Build.props b/Directory.Build.props
index fd024110..887804ab 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -27,6 +27,9 @@
$(NoWarn);Uno0001$(NoWarn);TKSMPL0014;
+
+
+ NU1901;NU1902;NU1903;NU1904
diff --git a/GenerateAllSolution.bat b/GenerateAllSolution.bat
index c4344e07..61551e18 100644
--- a/GenerateAllSolution.bat
+++ b/GenerateAllSolution.bat
@@ -1,5 +1,5 @@
@ECHO OFF
SET "MultiTargets=%1"
-IF "%MultiTargets%"=="" SET "MultiTargets=all"
+IF "%MultiTargets%"=="" SET "MultiTargets=uwp,wasdk,wasm"
powershell .\tooling\GenerateAllSolution.ps1 -MultiTargets %MultiTargets%
\ No newline at end of file
diff --git a/components/Behaviors/samples/InvokeActionsSample.xaml b/components/Behaviors/samples/InvokeActionsSample.xaml
index 47f1e748..f85645fa 100644
--- a/components/Behaviors/samples/InvokeActionsSample.xaml
+++ b/components/Behaviors/samples/InvokeActionsSample.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"
@@ -20,7 +19,7 @@
IsSequential="True">
-
@@ -53,12 +52,12 @@
-
-
+
-
+
diff --git a/components/Behaviors/samples/KeyDownTriggerBehaviorSample.xaml b/components/Behaviors/samples/KeyDownTriggerBehaviorSample.xaml
index 9594ef02..4938c2dc 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/Dependencies.props b/components/Behaviors/src/Dependencies.props
index aec754a1..c6da10ee 100644
--- a/components/Behaviors/src/Dependencies.props
+++ b/components/Behaviors/src/Dependencies.props
@@ -11,21 +11,21 @@
-
+
-
+
-
+
-
+
diff --git a/components/Converters/src/ColorToDisplayNameConverter.cs b/components/Converters/src/ColorToDisplayNameConverter.cs
index 6a22ff67..3aeecc81 100644
--- a/components/Converters/src/ColorToDisplayNameConverter.cs
+++ b/components/Converters/src/ColorToDisplayNameConverter.cs
@@ -38,7 +38,12 @@ public object Convert(
// Track https://github.com/unoplatform/uno/issues/18004
return "Not supported";
#elif WINUI2
+#if NET8_0_OR_GREATER
+ // Following advice from Sergio0694
+ return color.ToString();
+#else
return Windows.UI.ColorHelper.ToDisplayName(color);
+#endif
#elif WINUI3
return Microsoft.UI.ColorHelper.ToDisplayName(color);
#endif
diff --git a/components/Converters/src/ResourceNameToResourceStringConverter.cs b/components/Converters/src/ResourceNameToResourceStringConverter.cs
index ab9bf03b..e97a6bcc 100644
--- a/components/Converters/src/ResourceNameToResourceStringConverter.cs
+++ b/components/Converters/src/ResourceNameToResourceStringConverter.cs
@@ -32,18 +32,18 @@ public sealed partial class ResourceNameToResourceStringConverter : IValueConver
/// 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)
{
- var stringValue = value?.ToString();
- if (stringValue is null)
+ var valueAsString = value?.ToString();
+ if (valueAsString == null)
{
return string.Empty;
}
#if WINAPPSDK && !HAS_UNO
- return _resourceManager.MainResourceMap.TryGetValue(stringValue).ValueAsString;
+ return _resourceManager.MainResourceMap.TryGetValue(valueAsString).ValueAsString;
#else
- return _resourceLoader.GetString(stringValue) ?? string.Empty;
+ return _resourceLoader.GetString(valueAsString) ?? string.Empty;
#endif
}
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 @@
+
+
+
+
+
+
+
diff --git a/components/Extensions/samples/Dispatcher/MouseDebounceSample.xaml.cs b/components/Extensions/samples/Dispatcher/MouseDebounceSample.xaml.cs
new file mode 100644
index 00000000..c441f529
--- /dev/null
+++ b/components/Extensions/samples/Dispatcher/MouseDebounceSample.xaml.cs
@@ -0,0 +1,39 @@
+// 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(MouseDebounceSample), "DispatcherQueueTimer Debounce Mouse", description: "A sample for showing how to use the DispatcherQueueTimer Debounce extension to smooth mouse input.")]
+[ToolkitSampleNumericOption("Interval", 400, 300, 1000)]
+public sealed partial class MouseDebounceSample : Page
+{
+ public DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
+
+ private int _count = 0;
+
+ public MouseDebounceSample()
+ {
+ InitializeComponent();
+ }
+
+ private void Button_Click(object sender, RoutedEventArgs e)
+ {
+ _debounceTimer.Debounce(() =>
+ {
+ ResultText.Text = $"You hit the button {++_count} times!";
+ },
+ interval: TimeSpan.FromMilliseconds(Interval),
+ // By being on the leading edge, we ignore inputs past the first for the duration of the interval
+ immediate: true);
+ }
+}
diff --git a/components/Extensions/samples/DispatcherQueueTimerExtensions.md b/components/Extensions/samples/DispatcherQueueTimerExtensions.md
new file mode 100644
index 00000000..d740aad8
--- /dev/null
+++ b/components/Extensions/samples/DispatcherQueueTimerExtensions.md
@@ -0,0 +1,42 @@
+---
+title: DispatcherQueueTimerExtensions
+author: michael-hawker
+description: Helpers for executing code at specific times on a UI thread through a DispatcherQueue instance with a DispatcherQueueTimer.
+keywords: dispatcher, dispatcherqueue, DispatcherHelper, DispatcherQueueExtensions, DispatcherQueueTimer, DispatcherQueueTimerExtensions
+dev_langs:
+ - csharp
+category: Extensions
+subcategory: Miscellaneous
+discussion-id: 0
+issue-id: 0
+icon: Assets/Extensions.png
+---
+
+The `DispatcherQueueTimerExtensions` static class provides an extension method for [`DispatcherQueueTimer`](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.dispatching.dispatcherqueue) objects that make it easier to execute code on a specific UI thread at a specific time.
+
+The `DispatcherQueueTimerExtensions` provides a single extension method, `Debounce`. This is a standard technique used to rate-limit input from a user to not overload requests on an underlying service or query elsewhere.
+
+> [!WARNING]
+> You should exclusively use the `DispatcherQueueTimer` instance calling `Debounce` for the purposes of Debouncing one specific action/scenario only and not configure it for other additional uses.
+
+For each scenario that you want to Debounce, you'll want to create a separate `DispatcherQueueTimer` instance to track that specific scenario. For instance, if the below samples were both within your application. You'd need two separate timers to track debouncing both scenarios. One for the keyboard input, and a different one for the mouse input.
+
+> [!NOTE]
+> Using the `Debounce` method will set `DispatcherQueueTimer.IsRepeating` to `false` to ensure proper operation. Do not change this value.
+
+> [!NOTE]
+> If additionally registering to the `DispatcherQueueTimer.Tick` event (uncommon), it will be raised in one of two ways: 1. For a trailing debounce, it will be raised alongside the requested Action passed to the Debounce method. 2. For a leading debounce, it will be raised when the cooldown has expired and another call to Debounce would result in running the action.
+
+## Syntax
+
+It can be used in a number of ways, but most simply like so as a keyboard limiter:
+
+> [!SAMPLE KeyboardDebounceSample]
+
+Or for preventing multiple inputs from occuring accidentally (e.g. ignoring a double/multi-click):
+
+> [!SAMPLE MouseDebounceSample]
+
+## Examples
+
+You can find more examples in the [unit tests](https://github.com/CommunityToolkit/Windows/blob/rel/8.1.240916/components/Extensions/tests/DispatcherQueueTimerExtensionTests.cs).
diff --git a/components/Extensions/samples/ListViewExtensions.md b/components/Extensions/samples/ListViewExtensions.md
index 682cd597..877101e3 100644
--- a/components/Extensions/samples/ListViewExtensions.md
+++ b/components/Extensions/samples/ListViewExtensions.md
@@ -31,14 +31,7 @@ The `AlternateColor` property provides a way to assign a background color to eve
Here is how this property can be used in XAML:
-```xaml
-
-
-
-```
+> [!SAMPLE ListViewExtensionsAlternateColorSample]
## AlternateItemTemplate
diff --git a/components/Extensions/samples/ListViewExtensionsAlternateColorSample.xaml b/components/Extensions/samples/ListViewExtensionsAlternateColorSample.xaml
new file mode 100644
index 00000000..46f5fc41
--- /dev/null
+++ b/components/Extensions/samples/ListViewExtensionsAlternateColorSample.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+ One
+ Two
+ Three
+ Four
+ Five
+ Six
+ Seven
+ Eight
+ Nine
+ Ten
+
+
+
diff --git a/components/Extensions/samples/ListViewExtensionsAlternateColorSample.xaml.cs b/components/Extensions/samples/ListViewExtensionsAlternateColorSample.xaml.cs
new file mode 100644
index 00000000..20318e11
--- /dev/null
+++ b/components/Extensions/samples/ListViewExtensionsAlternateColorSample.xaml.cs
@@ -0,0 +1,17 @@
+// 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 ExtensionsExperiment.Samples;
+
+///
+/// An empty page that can be used on its own or navigated to within a Frame.
+///
+[ToolkitSample(id: nameof(ListViewExtensionsAlternateColorSample), nameof(ListViewExtensionsAlternateColorSample), description: $"A sample for showing how to use the ListViewExtensions.AlternateColor attached property.")]
+public sealed partial class ListViewExtensionsAlternateColorSample : Page
+{
+ public ListViewExtensionsAlternateColorSample()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/Extensions/src/Dispatcher/DispatcherQueueTimerExtensions.cs b/components/Extensions/src/Dispatcher/DispatcherQueueTimerExtensions.cs
index 9788fbca..769139de 100644
--- a/components/Extensions/src/Dispatcher/DispatcherQueueTimerExtensions.cs
+++ b/components/Extensions/src/Dispatcher/DispatcherQueueTimerExtensions.cs
@@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Concurrent;
+using System.Runtime.CompilerServices;
+
#if WINAPPSDK
using DispatcherQueueTimer = Microsoft.UI.Dispatching.DispatcherQueueTimer;
@@ -17,10 +19,11 @@ namespace CommunityToolkit.WinUI;
///
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/Text/StringExtensions.Localization.cs b/components/Extensions/src/Text/StringExtensions.Localization.cs
index f6f1193b..fb6a6f7b 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/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/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/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/Primitives/src/StaggeredLayout/StaggeredLayout.cs b/components/Primitives/src/StaggeredLayout/StaggeredLayout.cs
index 384d0614..28daed13 100644
--- a/components/Primitives/src/StaggeredLayout/StaggeredLayout.cs
+++ b/components/Primitives/src/StaggeredLayout/StaggeredLayout.cs
@@ -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/TokenizingTextBox/src/TokenizingTextBoxAutomationPeer.cs b/components/TokenizingTextBox/src/TokenizingTextBoxAutomationPeer.cs
index 8a9d1b87..333b2e1d 100644
--- a/components/TokenizingTextBox/src/TokenizingTextBoxAutomationPeer.cs
+++ b/components/TokenizingTextBox/src/TokenizingTextBoxAutomationPeer.cs
@@ -52,7 +52,13 @@ public void SetValue(string value)
{
if (IsReadOnly)
{
- throw new ElementNotEnabledException($"Could not set the value of the {nameof(TokenizingTextBox)} ");
+ #if WINDOWS_UWP
+ #if NET8_0_OR_GREATER
+ throw new Microsoft.UI.Xaml.Automation.ElementNotEnabledException($"Could not set the value of the {nameof(TokenizingTextBox)} ");
+ #else
+ throw new Windows.UI.Xaml.Automation.ElementNotEnabledException($"Could not set the value of the {nameof(TokenizingTextBox)} ");
+ #endif
+ #endif
}
this.OwningTokenizingTextBox.Text = value;
diff --git a/global.json b/global.json
index d413698a..27a18775 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "9.0.100",
+ "version": "9.0.101",
"rollForward": "latestFeature"
},
"msbuild-sdks":
diff --git a/nuget.config b/nuget.config
index c3f797ff..69f89a39 100644
--- a/nuget.config
+++ b/nuget.config
@@ -1,6 +1,5 @@
-
diff --git a/tooling b/tooling
index 89d14cb1..4fac6839 160000
--- a/tooling
+++ b/tooling
@@ -1 +1 @@
-Subproject commit 89d14cb12982634dd10e8908f5eda63a73a0a81f
+Subproject commit 4fac683906a559a36e27727d144b54df4f522895