在我的应用程序中,我将我的按钮绑定到带有 "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函数中?)
有什么方法可以实现这一点吗?
据我所知,这样扩展类命令是不可能的,因为 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
必须在主线程上被调用)。)
你可以使用 IsEnabled
属性,使 Button
不能点击,如以下代码。
<Button
Text="click"
Command={Binding Button1Command}
IsEnabled={Binding AreButtonsEnabled} />
如果 IsEnabled
是假的,你可以看到这个按钮,它是灰色的,如果你点击它,它将不会执行任何命令。
下面是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;
}
});
}