【问题标题】:Can I extend the command class so that the button that binds to it is automatically disabled?我可以扩展命令类,以便自动禁用绑定到它的按钮吗?
【发布时间】:2020-05-04 09:00:16
【问题描述】:

在我的应用程序中,我将按钮绑定到带有“MVVM”的命令。 我的命令实现如下:

        public Command CommandLoadStuff
        {
            get
            {
                return new Command(async () =>
                {
                    await DoLongStuff();
                });
            }
        }

问题是这些命令是异步的,用户可以多次单击它们,导致代码也执行多次。

作为第一种方法,我使用了 CanExecute:

        public Command CommandLoadStuff
        {
            get
            {
                return new Command(async () =>
                {
                    AppIsBusy = true;
                    await DoLongStuff();
                    AppIsBusy = false;
                },() => !AppIsBusy);
            }
        }

现在我想知道是否有比单独处理每个命令的 CanExecute 更好的方法。

由于我每次都用“new”初始化命令,我想知道“Command”类是否不能相应地扩展。它应该在 CanExecute 的生命周期内阻止第二次单击按钮(可能在构造函数中?)并在命令执行完成后释放它。 (可能在 Dispose 函数中?)

有没有办法做到这一点?

【问题讨论】:

    标签: c# xaml mvvm xamarin.forms


    【解决方案1】:

    据我所知,以这种方式扩展类命令是不可能的,因为Execute 是非虚拟的,您必须将execute 操作传递给构造函数。不管怎样,还是有办法的。 Command 派生自ICommand,具有以下接口

    public interface ICommand
    {
        event EventHandler CanExecuteChanged;
    
        void Execute(object data);
    
        bool CanExecute(object data);
    }
    

    您可以创建一个类AsyncBlockingCommand(或其他),它将返回CanExecute 的相应值,具体取决于异步方法是否仍在运行(我知道async void 方法存在问题,所以小心处理)

    public class AsyncBlockingCommand : ICommand
    {
        bool _canExecute = true;
        Func<Task> _toExecute;
    
        public AsyncBlockingCommand(Func<Task> toExecute)
        {
            _toExecute = toExecute;
        }
    
        public event EventHandler CanExecuteChanged;
    
        public async void Execute(object data)
        {
            _canExecute = false;
            RaiseCanExecuteChanged();
            await _toExecute();
            _canExecute = true;
            RaiseCanExecuteChanged();
        }
    
        public bool CanExecute(object data) => _canExecute; 
    
        private void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
    

    在执行异步方法之前,_canExecute 设置为 false 并引发 CanExecuteChanged。这样,您的Button 将收到CanExecute 已更改并禁用自身的通知。在调用异步方法后反之亦然。 (可能RaiseCanExecuteChanged 必须在主线程上调用。)

    【讨论】:

    • 非常感谢!这类似于我做的实验,但我忘了等待执行,所以我的命令立即再次启用!你的剪报将解决我的问题。谢谢! :) -> 我编辑了您的帖子,因为调用函数需要 2 个参数。 CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    • 当然事件需要参数。感谢您的编辑。
    【解决方案2】:

    您可以使用IsEnabled 属性使Button 不能被点击。 就像下面的代码。

    <Button 
        Text="click"
        Command={Binding Button1Command}
        IsEnabled={Binding AreButtonsEnabled} />
    

    如果IsEnabled的值为false,你可以看到这个按钮,它是灰色的,如果你点击它,它不会执行任何命令。

    这是 MyViewModel 代码。

    private bool _areButtonsEnabled = true;
    
    public bool AreButtonsEnabled
    {
        get => _areButtonsEnabled;
        set
        {
            if (_areButtonsEnabled != value)
            {
                _areButtonsEnabled = value;
                OnPropertyChanged(nameof(AreButtonsEnabled)); // assuming your view model implements INotifyPropertyChanged
            }
        }
    }
    
    public ICommand Button1Command { get; protected set; }
    
    public MyViewModel()
    {
        Button1Command = new Command(HandleButton1Tapped);
    }
    
    private void HandleButton1Tapped()
    {
        // Run on the main thread, to make sure that it is getting/setting the proper value for AreButtonsEnabled
        // And note that calls to Device.BeginInvokeOnMainThread are queued, therefore
        // you can be assured that AreButtonsEnabled will be set to false by one button's command
        // before the value of AreButtonsEnabled is checked by another button's command.
        // (Assuming you don't change the value of AreButtonsEnabled on another thread)
        Device.BeginInvokeOnMainThread(async() => 
        {
            if (AreButtonsEnabled)
            {
                AreButtonsEnabled = false;
                // DoLongStuff code
                await  Task.Delay(2000);
                AreButtonsEnabled = true;
            }
        });
    }
    

    【讨论】:

    • 一些想法:BebinInvokeOnMainThread 是从应该已经在主线程上的方法调用的。因此,我认为你把它放在哪里没有必要。此外,您希望避免在主线程上进行长时间运行的计算。
    猜你喜欢
    • 2014-04-09
    • 1970-01-01
    • 1970-01-01
    • 2019-06-21
    • 1970-01-01
    • 1970-01-01
    • 2018-10-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多