【问题标题】:CanExecute in Prism DelegateCommand not workingPrism DelegateCommand 中的 CanExecute 不起作用
【发布时间】:2019-02-15 17:24:15
【问题描述】:

当我在短时间内多次单击按钮时,每次都会调用该方法并且我的应用程序崩溃(当代码尝试导航到另一个页面时)。此问题仅在 Xamarin.Android 中出现(iOS 处理双击)

public bool IsBusy { get; set; }

private DelegateCommand<string> _eventDetailsCommand;
public DelegateCommand<string> EventDetailsCommand => _eventDetailsCommand ?? (_eventDetailsCommand = new DelegateCommand<string>(EventDetails, (x) => !IsBusy));

private void EventDetails(string obj)
{
    IsBusy = true;
    await _navigationService.NavigateAsync("AnotherPage");
    IsBusy = false;
}

Xamarin.Android

棱镜:7.1.0.172(预)

PropertyChanged.Fody (2.2.6):

【问题讨论】:

  • 即使很明显这里有什么问题,您也应该编辑您的问题以扩展究竟是什么不起作用您希望您的代码做什么...
  • 是的,你是对的

标签: xamarin.forms xamarin.android prism


【解决方案1】:

最重要的一点是DelegateCommand.RaiseCanExecuteChanged

你应该先将CanNavigate设置为false,按钮将被禁用,在按钮命令方法代码完成后再设置为true

这是我的例子,它工作正常:

    private bool _canNavigate = true;
    public bool CanNavigate
    {
        get { return _canNavigate; }
        set { 
            SetProperty(ref _canNavigate, value);
            LoginCommand.RaiseCanExecuteChanged();
        }
    }

    private DelegateCommand _loginCommand;
    public DelegateCommand LoginCommand => _loginCommand ?? (_loginCommand = new DelegateCommand(Login, () => CanNavigate));
    public async void Login()
    {
        CanNavigate = false;
        
        ...do something here

        CanNavigate = true;
    }

【讨论】:

    【解决方案2】:

    仅设置 IsBusy 不起作用,因为当 IsBusy 更改时命令不会收到通知,UI 也不会。您必须使用 RaiseCanExecuteChanged 通知所有人。更好的方法是在创建 DelegateCommand 之后使用 ObservesCanExecute(Fluent API,但请注意只能观察到一个属性)。它会为您处理这些,并会自动调用 RaiseCanExecuteChanged。

    这是我通常如何处理的示例:

    public MyViewModel(): ViewModelBase
    {
        private readonly INavigationService _navigationService;
        public bool IsBusy { get; set; }
    
        public ICommand ShowEventDetailsCommand { get; private set; }
    
        public  MyViewModel(INavigationService navService)
        {
            _navigationService = navService;
            ShowEventDetailsCommand = new DelegateCommand<string>(async(obj) => await ExecuteShowEventDetailsCommand(obj)).ObservesCanExecute(() => !IsBusy);
        }
    
        public async Task ExecuteShowEventDetailsCommand(obj)
        {
            IsBusy = true; // Note this is not thread safe, just for demonstration
            try
            {
                await _navigationService.NavigateAsync(...);
            }
            finally
            {
                IsBusy = false;
            }
        }
    }
    

    这通常是我处理这个问题的方式。但请注意,对 IsBusy 的访问不是线程安全的,因此您应该使用它。我有类似 LockActivityHandler 的东西,带有 .TryLock、.Unlock 和 .IsLocked。

    if(LockActivityHandler.TryLock())
    {
        try
        {
            //DoStuff
        }
        finally
        {
            LockActivityHandler.Unlock();
        }
    }
    

    IsLocked 可以绑定到 UI 元素的 Enabled 属性以禁用它们。即使那些没有被禁用并且另一个动作正在运行,由于 TryLock() => false

    ,新的动作也不会被执行

    PS:这在文档中也有更多的例子,所以你可以看看这里:https://prismlibrary.github.io/docs/commanding.html

    【讨论】:

      【解决方案3】:

      请尝试

      _deviceService.BeginInvokeOnMainThread(() =>
          {
              IsBusy = true;
          });
          //await ... long running process.
      

      在上面的代码 sn-p 中,_deviceService 是通过构造函数注入的 IDeviceService 类型,如下所示:

      private IDeviceService _deviceService;
      
      /// <summary>
      /// Class constructor 
      /// </summary>
      /// <param name="deviceService"></param>
      public MyPageViewModel(IDeviceService deviceService) {
          _deviceService = deviceService;
      }
      

      【讨论】:

      • @Haukinger 可能是对的,出现这个问题的原因是我尝试在我的方法中导航到另一个页面(当我在屏幕上写一些文本时它可以工作,但是当我导航时它不起作用)
      【解决方案4】:

      private void EventDetails(字符串 obj) { 忙=真; …… 忙=假; }

      这会在事件处理程序执行的整个过程中阻塞 UI 线程,因此IsBusy = true;IsBusy = false; 对 UI 没有任何可观察到的影响。

      这是async 的教科书示例。

      你应该这样写:

      private async void EventDetails(string obj)
      {
          IsBusy = true;
      
          await ....;
      
          IsBusy = false;
      }
      

      如果 .... 碰巧不是可等待的,请将其包装在 Task.Run 中:

      private async void EventDetails(string obj)
      {
          IsBusy = true;
      
          await Task.Run( () => .... );
      
          IsBusy = false;
      }
      

      【讨论】:

      • 我调用了 await NavigationService.NavigateAsync() 来代替点导航到另一个页面
      • 请说明您希望您的代码做什么。如果我没记错NavigateAsync 开始导航,等待它只会等待导航开始,直到导航完成,但我可能错了。
      • 我的代码应该通过 Prism 的导航服务导航到另一个页面。但是现在,在双击 listview 项目期间,我的应用程序崩溃了,因为该命令被调用了两次(此行为仅在 Android 上发生)
      • 你可能是对的,这个问题与导航有关。你有什么解决办法吗?
      • 为了防止崩溃,在设置为true之前检查IsBusy,如果已经是true就返回。您可能还需要延迟将其重置为 false,直到导航完成。
      猜你喜欢
      • 2017-08-21
      • 2012-03-28
      • 2011-11-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-15
      • 2017-05-12
      • 2014-12-13
      相关资源
      最近更新 更多