You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Improvements to DispatcherQueueTimer.Debounce extension (#569)
* Add baseline single scenario tests for DispatcherQueueTimer Debounce
* Add some DispatcherQueueTimer docs, initial sample, and more unit tests
TODO: Still have the opposite scenario of Trailing to leading which is broken to fix.
* Add test and fix behavior for when switching Debounce mode from Trailing to Leading for a DispatcherQueueTimer
* Add Debounce test for stopping the timer manually
* Add mouse clicking debounce sample
* Clean-up usage of the sample slider value in the Debounce samples
* Switch Debounce DispatcherQueueTimer extension to use ConditionalWeakTable
This prevents capture holding onto the timer for garbage collection, validated with a unit test which fails with the ConcurrentDictionary, but succeeds with the ConditionalWeakTable
Because of the new Trailing/Leading behavior, we need the table in order to know if something was scheduled, otherwise we'd need reflection if we only stored the event handler, which wouldn't be AOT compatible.
* Apply XAML Styler
* Add additional notes/details about how to use the DispatcherQueueTimer.Debounce method to the docs
* Clarify behavior in docs and test results of registering to the Tick event of the DispatcherQueueTimer when using Debounce
Behavior should be well defined now.
In the future, we could define a 'repeating' behavior, if we think it'd be useful (not sure of the specific scenario), but to do so, I would recommend we encorporate it at the end of the current signature and make false by default:
public static void Debounce(this DispatcherQueueTimer timer, Action action, TimeSpan interval, bool immediate = false, bool repeat = false)
I would imagine, this would do something like continually pulse the Action/Tick event but when additional requests are received that it would disrupt that periodic pattern somehow based on the debounce configuration (trailing/leading)?
* Apply Arlo's suggestions from code review on Debounce improvements - Thanks!
Co-authored-by: Arlo <arlo.godfrey@outlook.com>
---------
Co-authored-by: Arlo <arlo.godfrey@outlook.com>
[ToolkitSample(id:nameof(KeyboardDebounceSample),"DispatcherQueueTimer Debounce Keyboard",description:"A sample for showing how to use the DispatcherQueueTimer Debounce extension to smooth keyboard input.")]
[ToolkitSample(id:nameof(MouseDebounceSample),"DispatcherQueueTimer Debounce Mouse",description:"A sample for showing how to use the DispatcherQueueTimer Debounce extension to smooth mouse input.")]
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.
16
+
17
+
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.
18
+
19
+
> [!WARNING]
20
+
> 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.
21
+
22
+
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.
23
+
24
+
> [!NOTE]
25
+
> Using the `Debounce` method will set `DispatcherQueueTimer.IsRepeating` to `false` to ensure proper operation. Do not change this value.
26
+
27
+
> [!NOTE]
28
+
> 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.
29
+
30
+
## Syntax
31
+
32
+
It can be used in a number of ways, but most simply like so as a keyboard limiter:
33
+
34
+
> [!SAMPLE KeyboardDebounceSample]
35
+
36
+
Or for preventing multiple inputs from occuring accidentally (e.g. ignoring a double/multi-click):
37
+
38
+
> [!SAMPLE MouseDebounceSample]
39
+
40
+
## Examples
41
+
42
+
You can find more examples in the [unit tests](https://github.com/CommunityToolkit/Windows/blob/rel/8.1.240916/components/Extensions/tests/DispatcherQueueTimerExtensionTests.cs).
/// <para>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.</para>
26
+
/// <para>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.</para>
24
27
/// <para>Use this method to control the timer instead of calling Start/Interval/Stop manually.</para>
25
28
/// <para>A scheduled debounce can still be stopped by calling the stop method on the timer instance.</para>
26
29
/// <para>Each timer can only have one debounced function limited at a time.</para>
27
30
/// </summary>
28
31
/// <param name="timer">Timer instance, only one debounced function can be used per timer.</param>
29
32
/// <param name="action">Action to execute at the end of the interval.</param>
30
33
/// <param name="interval">Interval to wait before executing the action.</param>
31
-
/// <param name="immediate">Determines if the action execute on the leading edge instead of trailing edge.</param>
34
+
/// <param name="immediate">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.</param>
0 commit comments