Blazor Custom Longpress -Taste - Stellen Sie sicher, dass die Aktion nur einmal auslöstC#

Ein Treffpunkt für C#-Programmierer
Anonymous
 Blazor Custom Longpress -Taste - Stellen Sie sicher, dass die Aktion nur einmal auslöst

Post by Anonymous »

Wir haben eine Blazor -Web -Benutzeroberfläche mit Mudblazor -Komponenten, die auf einem Kiosk -PC mit einem TouchScRen ausgeführt werden. Das Problem ist, dass unsere Benutzer häufig ihren Finger auf einen Knopf legen und sie dort einfach halten. Auf diese Weise schießt das Onclick -Ereignis nicht, also passiert nichts. Dann beschweren sie sich, dass unser Kiosk nicht funktioniert. /> [*] Die Normal -Taste drückt immer noch < /li>
< /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
We're using MudDialog instances for our dialogs. I've replaced the buttons in the dialog template with KioskButtons. I've added a parameter for the dialog:
[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)
There's an interesting sub-problem. I have a screen with a text input and a button. When the button is pressed, it performs a search based on the input, and either shows a dialog or navigates to another page based on the result. When I select the text input, an on-screen keyboard called Squeekboard pops up. This is a "system" keyboard, completely outside the browser. In fact it resizes the browser window so it can fit underneath it. But here's the fun part: if I just tap on the button, the keyboard disappears, the browser resizes back, and then the dialog is shown. But if I do the longpress, the keyboard remains and the dialog is shown - shifted up as everything is resized. I assumed the text input doesn't get de-focused properly, so I tried to do this manually. Blazor components don't have a "Blur" method, so I tried focusing the button instead.
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
On the aforementioned screen, if the input box is empty, the button should be disabled. But your solution uses the Disabled parameter for disabling the button during execution. I've tried to somehow combine the logic, but I'm getting inconsistent behavior.
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.

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post