You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor自定义长按按钮:解决重复触发及衍生功能问题

解决Blazor MudButton触摸屏长按重复触发及衍生问题

核心问题:长按/点击动作重复触发修复

问题根源

原代码中Task.Delay.ContinueWith使用了TaskScheduler.Default,导致ExecuteAction在后台线程执行,而_actionExecuted作为共享状态未做线程同步保护,加上NavigationManager.NavigateTo的同步特性,会导致按钮状态重置异常,进而引发动作重复触发。

修复方案

  1. 改用await替代ContinueWith,确保代码在Blazor UI线程上下文执行;
  2. 用Interlocked保证_actionExecuted的线程安全;
  3. 优化状态重置逻辑,导航操作后不重置按钮状态。

修复后的核心代码:

@inherits MudButton
@inject IJSRuntime _jsRuntime

<MudButton @onmousedown="StartTimer"
           @onmouseup="HandleRelease"
           @ontouchstart="StartTimer"
           @ontouchend="HandleRelease"
           Disabled="@(Disabled || _isExecuting)">
    @ChildContent
</MudButton>

@code {
    [Parameter] public EventCallback<object> OnAction { get; set; }
    [Parameter] public int HoldDuration { get; set; } = 300;
    [Parameter] public string DataTestId { get; set; }
    [Parameter] public bool Disabled { get; set; }

    private CancellationTokenSource _cts;
    private bool _actionExecuted;
    private bool _isExecuting;
    private bool _isNavigating; // 标记是否为导航操作

    private async Task StartTimer()
    {
        StopTimer();
        _actionExecuted = false;
        _isExecuting = false;
        _cts = new CancellationTokenSource();

        try
        {
            await Task.Delay(HoldDuration, _cts.Token);
            if (!_cts.Token.IsCancellationRequested)
            {
                await ExecuteAction(true);
            }
        }
        catch (TaskCanceledException)
        {
            // 定时器被取消,无需处理
        }
    }

    private async Task HandleRelease()
    {
        StopTimer();
        await ExecuteAction(false);
        // 收起软键盘
        await _jsRuntime.InvokeVoidAsync("window.blurActiveElement");
    }

    private void StopTimer()
    {
        _cts?.Cancel();
        _cts?.Dispose();
        _cts = null;
    }

    private async Task ExecuteAction(bool fromTimer)
    {
        string origin = fromTimer ? "timer" : "touch end";
        
        // 线程安全的状态检查
        if (Interlocked.CompareExchange(ref _actionExecuted, true, false) || _isExecuting || Disabled)
        {
            Console.WriteLine($" KioskButton - {origin} - {DataTestId} - action tried to double-fire.");
            return;
        }

        _isExecuting = true;
        try
        {
            Console.WriteLine($"a) KioskButton - {origin} - {DataTestId} - action triggered and passed check.");

            if (OnAction.HasDelegate)
            {
                Console.WriteLine($"b) KioskButton - {origin} - {DataTestId} - performing OnAction.");
                await OnAction.InvokeAsync(Parameters);
            }
        }
        finally
        {
            // 导航操作后不重置状态,避免重复触发
            if (!_isNavigating)
            {
                _isExecuting = false;
                _actionExecuted = false;
            }
        }
    }

    // 暴露给外部设置导航状态的方法
    public void SetNavigating(bool isNavigating)
    {
        _isNavigating = isNavigating;
    }
}

调用导航时先标记状态:

await kioskButtonRef.SetNavigating(true);
NavigationManager.NavigateTo("/target-page");

衍生问题逐一解决

1. MudDialog调度器异常修复

调用对话框关闭时引发调度器异常,通常是因为操作不在UI线程上下文或对话框已关闭。

解决方案:

  • 用InvokeAsync包裹对话框操作,确保在UI线程执行;
  • 先检查对话框状态,避免无效操作;
  • 捕获异常防止崩溃。

示例代码:

private async Task CloseDialog()
{
    try
    {
        if (Dialog != null && Dialog.IsOpen)
        {
            await InvokeAsync(() => Dialog.Close(DialogResult.Ok(true)));
            await StateHasChangedAsync();
        }
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine($"Dialog close error: {ex.Message}");
        // 异常处理逻辑
    }
}

2. Squeekboard软键盘自动收起问题

长按按钮后软键盘未自动收起,手动聚焦按钮会导致对话框无法显示。

解决方案:

  • 在wwwroot/index.html添加全局JS方法:
<script>
    window.blurActiveElement = () => {
        const activeElement = document.activeElement;
        if (activeElement && activeElement.tagName !== 'BODY') {
            activeElement.blur();
        }
    };
</script>
  • 在按钮释放时调用该方法(已包含在核心修复代码的HandleRelease中)。

3. 按钮禁用逻辑冲突处理

外部传入的Disabled参数与内部执行时的禁用逻辑冲突,需合并两者状态。

解决方案:

  • 内部维护_isExecuting标记按钮是否正在执行动作;
  • 在MudButton的Disabled属性绑定Disabled || _isExecuting,合并外部禁用和内部执行状态;
  • 在ExecuteAction中优先检查外部Disabled状态,避免无效触发。

核心绑定逻辑已包含在组件代码中:Disabled="@(Disabled || _isExecuting)"


内容的提问来源于stack exchange,提问作者Shaggydog

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 10:47:06