.NET MAUI中快速点击按钮触发多次导航的优化方案问询
问题
我在Xamarin.Forms应用中使用以下代码做页面导航:
await Shell.Current.Navigation.PushAsync(new MyAccountsTabbedPage(_accountsViewModel, tcode, _supportPageViewModel, _contactusPageViewModel, _faqCategoryPageViewModel));
快速多次点击按钮时,会触发多次导航,打开多个MyAccountsTabbedPage页面,体验很差。
目前我尝试了两种方案:
- 导航前检查导航栈的最后一页类型:
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));
- 控制按钮的
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
相关产品推荐
相关产品推荐

