< /ul>
Wie ich es implementiert habe:
auf Ontouchstart Ein 300 -ms -Timer wird gestartet (oder ein Aufgabe.Delay). gestoppt < /li>
Das "_actionexecuted" -Bol wird überprüft. Wenn es falsch ist, ist es sofort auf True eingestellt und die Aktion wird ausgeführt. Wenn es bereits stimmt, geschieht nichts, ich kehre nur zurück.@inherits MudButton
@ChildContent
< /code>
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;
}
< /code>
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?
Edit:
This is in response to MrC aka Shaun Curtis.
Thanks again for your help.
Yes, there is more I wasn't showing. I didn't want to describe the full extent of what I'm trying to do, as I would likely get knocked for "asking more than one question" and "trying to get people to do my work for me". So I boiled it down to the logic that I naively believed was the only problem and posted that. Then I integrated your logic instead of mine. It helped, but then other issues cropped up. I will post the full problem below.
But first, I seem to have figured out the double-firing issue. I put in the 2-second delay, followed by NavigationManager.NavigateTo(). I observed something strange:
On the first try after deploying the app, I tapped the button, it went grey for two seconds, then it got enabled again, and a moment after that navigation to the next page happened. On second try the navigation happened a lot faster and I didn't observe this inconsistency.
It seems that when I call NavigationManager.NavigateTo(), it triggers a parallel process that consists of loading the new page and then navigating to it. But NavigationManager.NavigateTo() is not async, it can't be awaited, so my code just continues running. The action executes, the button becomes available, and if the user still has their finger on it, it gets triggered. (on the second try the next page is already cached, so the whole navigation happens much faster and there isn't time for this to happen). Knowing this I managed to solve the double-fire issue by adding a simple rule:
If you're going to navigate to another page, don't re-enable the
button.
Here's my full scope:
I'm making a button component, that ideally should replace MudButton as seamlessly as possible. I'm using it like so:
// button with action
Do The Thing
//button with link
Go to next page
// definition of OnAction and Parameters
[Parameter] public Func? OnAction { get; set; }
[Parameter] public object[] Parameters { get; set; } = Array.Empty();
< /code>
The button takes the function to execute as parameter, and also needs to work with the Disabled parameter - i.e. it can be disabled based on external conditions, not just when its action is being executed.
Crucially, the buttons need to work everywhere, including dialogs. This brings me to the issues I've been having:
- Those damn dialogs
[Parameter] public Action? OnActionVoid { get; set; }
< /code>
because for MudDialog you need to call these void methods:
void Submit() => MudDialog.Close(DialogResult.Ok(true));
void Cancel() => MudDialog.Cancel();
< /code>
However I've learned there is a threading issue, as I need to call the method like this:
if (OnActionVoid != null)
{
await InvokeAsync(() =>
{
Console.WriteLine($"b) KioskButton - {DataTestId} - performing OnActionVoid - {origin}");
OnActionVoid(Parameters);
StateHasChanged();
});
}
< /code>
Otherwise the dialog stays open when the button is clicked. However this seems to be causing the dispatcher issue. Though it seems to be only in conjunction with the button functionality - Button A triggers a dialog, Button B on the dialog closes it using IvokeAsync, and then there's an attempt to update the state of Button A, and the exception occurs.
- a)
if (_submitButton != null)
{
await _submitButton.FocusAsync();
StateHasChanged();
}
< /code>
This caused the dialog to not show up. I've tried a few things, results ranged from not working to throwing exceptions.
- Disabling the button
First of all, Blazor won't allow me to define the Disabled parameter on my own component and hide the one its base MudButton. So I've been trying to combine the values on OnParametersSet, and then refresh before and after execution. But the Disabled value from outside can change over time, so it's inconsistent. Though I suppose I can solve this by creating a custom IsDisabled parameter.