We have a Blazor web UI with MudBlazor components, running on a kiosk PC with a touchscren. Problem is our users frequently place their finger on a button and just hold them there. This way the OnClick event doesn't fire, so nothing happens. Then they complain that our kiosk isn't working.
I've been developing a custom button that reacts to a "longpress" as well as a normal tap.The intended functionality is as follows:
- if the user presses and holds the button, after 300ms the button registers it as a regular press and triggers its action.
- the normal button press still works
How I've implemented it:on ontouchstart a 300ms timer is started (or a Task.Delay).after the timer has elapsed, or an ontouchend event is fired, the following happens:
- timer is stopped
- the "_actionExecuted" bool is checked. If it's false, it's immediately set to true and the action is executed. If it's already true, nothing happens, I just return.
It seems pretty straightforward, but even with the check, the action sometimes gets triggered twice.
Here's my code (abridged):
@inherits MudButton<MudButton @onmousedown="StartTimer" @onmouseup="HandleRelease" @ontouchstart="StartTimer" @ontouchend="HandleRelease"> @ChildContent</MudButton> private async Task ExecuteAction(bool fromTimer) { string origin = fromTimer ? "timer" : "touch end"; if (_actionExecuted) { Console.WriteLine($" KioskButton - {origin} - {DataTestId} - action tried to double-fire."); return; }// Prevent multiple calls _actionExecuted = true; Console.WriteLine($"a) KioskButton - {origin} - {DataTestId} - action triggered and passed check."); if (OnAction != null) { Console.WriteLine($"b) KioskButton - {origin} - {DataTestId} - performing OnAction."); await OnAction(Parameters); } } private void StartTimer() { StopTimer(); // Ensure no previous timer is running _actionExecuted = false; _cts = new CancellationTokenSource(); Task.Delay(HoldDuration, _cts.Token).ContinueWith(async t => { if (!t.IsCanceled) { await ExecuteAction(true); } }, TaskScheduler.Default); } private async Task HandleRelease() { StopTimer(); // Stop the hold timer await ExecuteAction(false); // Execute if not already triggered } private void StopTimer() { _cts?.Cancel(); _cts?.Dispose(); _cts = null; }I was under the impression this code should have prevented double-firing of the action. But it does happen, actually quite often - maybe one out of 5 clicks (although when I try to reproduce it on purpose, it stops happening). Is there a way to make absolutely sure the button never double-fires?