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

.NET MAUI中快速点击按钮触发多次导航的优化方案问询

问题

我在Xamarin.Forms应用中使用以下代码做页面导航:

await Shell.Current.Navigation.PushAsync(new MyAccountsTabbedPage(_accountsViewModel, tcode, _supportPageViewModel, _contactusPageViewModel, _faqCategoryPageViewModel));

快速多次点击按钮时,会触发多次导航,打开多个MyAccountsTabbedPage页面,体验很差。

目前我尝试了两种方案:

  1. 导航前检查导航栈的最后一页类型:
if (Shell.Current.Navigation.NavigationStack.Count == 0 || Shell.Current.Navigation.NavigationStack.Last().GetType() != typeof(MyAccountsTabbedPage))
    await Shell.Current.Navigation.PushAsync(new MyAccountsTabbedPage(_accountsViewModel, tcode, _supportPageViewModel, _contactusPageViewModel, _faqCategoryPageViewModel));
  1. 控制按钮的IsEnabled属性,但这个方案体验不好。

按钮点击事件的完整代码如下:

private async void ViewStatements_Clicked(object sender, EventArgs e) {
  var button = (Button) sender;
  _accountsViewModel.SelectedLease = (LeaseInfo) button.CommandParameter;
  string tcode = _accountsViewModel.SelectedLease.TenantCode;
  await _accountsViewModel.IsServiceChargeVisible(tcode);
  if (Shell.Current.Navigation.NavigationStack.Count == 0 || Shell.Current.Navigation.NavigationStack.Last().GetType() != typeof (MyAccountsTabbedPage))
    await Shell.Current.Navigation.PushAsync(new MyAccountsTabbedPage(_accountsViewModel, tcode, _supportPageViewModel, _contactusPageViewModel, _faqCategoryPageViewModel));
}

想找更完善的实现,让多次点击按钮的效果和单次点击一致。

更优解决方案

1. 用异步锁(SemaphoreSlim)防止重复触发

在页面类或ViewModel中声明SemaphoreSlim实例,限制同一时间只能执行一次导航逻辑,彻底阻断快速重复点击的触发:

// 在页面类或ViewModel中定义
private readonly SemaphoreSlim _navigationLock = new SemaphoreSlim(1, 1);

private async void ViewStatements_Clicked(object sender, EventArgs e) {
  // 尝试获取锁,若当前已有导航在执行则直接返回
  if (!_navigationLock.Wait(0)) return;

  try {
    var button = (Button)sender;
    _accountsViewModel.SelectedLease = (LeaseInfo)button.CommandParameter;
    string tcode = _accountsViewModel.SelectedLease.TenantCode;
    await _accountsViewModel.IsServiceChargeVisible(tcode);

    // 可选:保留导航栈检查,避免不必要的页面实例创建
    if (Shell.Current.Navigation.NavigationStack.Count == 0 || Shell.Current.Navigation.NavigationStack.Last().GetType() != typeof(MyAccountsTabbedPage))
      await Shell.Current.Navigation.PushAsync(new MyAccountsTabbedPage(_accountsViewModel, tcode, _supportPageViewModel, _contactusPageViewModel, _faqCategoryPageViewModel));
  } finally {
    // 无论逻辑是否执行完成,都释放锁
    _navigationLock.Release();
  }
}

2. 利用Shell的语义化导航(GoToAsync)

Shell原生支持路由导航,注册路由后使用GoToAsync会自动处理重复跳转:若目标页面已在导航栈中,会直接切换到该页面,而非重复Push新实例。

第一步:注册路由(App.xaml.cs构造函数中)

Routing.RegisterRoute(nameof(MyAccountsTabbedPage), typeof(MyAccountsTabbedPage));

第二步:修改导航逻辑

private async void ViewStatements_Clicked(object sender, EventArgs e) {
  var button = (Button)sender;
  _accountsViewModel.SelectedLease = (LeaseInfo)button.CommandParameter;
  string tcode = _accountsViewModel.SelectedLease.TenantCode;
  await _accountsViewModel.IsServiceChargeVisible(tcode);

  // 通过路由导航,自动处理重复跳转
  await Shell.Current.GoToAsync($"//{nameof(MyAccountsTabbedPage)}", true, new Dictionary<string, object>
  {
      {"AccountsViewModel", _accountsViewModel},
      {"TCode", tcode},
      {"SupportPageViewModel", _supportPageViewModel},
      {"ContactUsPageViewModel", _contactusPageViewModel},
      {"FaqCategoryPageViewModel", _faqCategoryPageViewModel}
  });
}

第三步:在目标页面接收参数

在MyAccountsTabbedPage的OnNavigatedTo方法中获取传递的参数:

protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
    base.OnNavigatedTo(args);
    if (Shell.Current.CurrentState.NavigationParameters.TryGetValue("AccountsViewModel", out var accountsVm))
    {
        BindingContext = accountsVm as AccountsViewModel;
    }
    // 其他参数同理获取
}

3. 封装统一导航服务(适合大型项目)

把导航逻辑抽成全局服务,统一处理重复点击、导航栈检查等逻辑,避免代码冗余:

public interface INavigationService
{
    Task NavigateToAsync<T>(params object[] parameters) where T : Page;
}

public class NavigationService : INavigationService
{
    private readonly SemaphoreSlim _navigationLock = new SemaphoreSlim(1, 1);

    public async Task NavigateToAsync<T>(params object[] parameters) where T : Page
    {
        if (!_navigationLock.Wait(0)) return;

        try
        {
            var targetType = typeof(T);
            var lastPage = Shell.Current.Navigation.NavigationStack.LastOrDefault();
            
            // 检查栈顶是否为目标页面,是则直接返回
            if (lastPage != null && lastPage.GetType() == targetType) return;

            // 创建页面实例并导航
            var page = Activator.CreateInstance(targetType, parameters) as Page;
            await Shell.Current.Navigation.PushAsync(page);
        }
        finally
        {
            _navigationLock.Release();
        }
    }
}

在页面中注入并使用服务:

private readonly INavigationService _navigationService;

// 构造函数注入导航服务
public YourPage(INavigationService navigationService)
{
    InitializeComponent();
    _navigationService = navigationService;
}

private async void ViewStatements_Clicked(object sender, EventArgs e) {
  var button = (Button)sender;
  _accountsViewModel.SelectedLease = (LeaseInfo)button.CommandParameter;
  string tcode = _accountsViewModel.SelectedLease.TenantCode;
  await _accountsViewModel.IsServiceChargeVisible(tcode);

  await _navigationService.NavigateToAsync<MyAccountsTabbedPage>(_accountsViewModel, tcode, _supportPageViewModel, _contactusPageViewModel, _faqCategoryPageViewModel);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 02:25:32