如何创建一个自定义按钮,其中包含根据AsyncCommand中的IsExecuting而变化的图像?不需要创建UserControl,只需继承XAML中的Button(
public class CustomButton : Button
)和Style即可实现。
public class AsyncCommand : ViewModelBase, ICommand
{
protected readonly Func<Task> _execute;
protected readonly Func<bool>? _canExecute;
private bool _isExecuting;
public bool IsExecuting
{
get => _isExecuting;
set => RisePropertyChanged(ref _isExecuting, value);
}
public AsyncCommand(Func<Task> execute, Func<bool>? canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter) => !_isExecuting && (_canExecute?.Invoke() ?? true);
public async void Execute(object? parameter)
{
if (CanExecute(parameter))
{
try
{
IsExecuting = true;
await ExecuteAsync(parameter);
}
finally
{
IsExecuting = false;
}
}
OnCanExecuteChanged();
}
protected virtual async Task ExecuteAsync(object? parameter) => await _execute();
protected virtual void OnCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
也许可以使用
event EventHandler? CanExecuteChanged
,它在 ICommand 中使用,并由 Button 做出反应,而不是 RaisePropertyChanged,但我已经没有想法了。
我找了很多地方,尝试了很多方法来实现它,但没有成功。
您可以使用
Style
和 DataTrigger
来实现您的行为。您可以将此 Style
定义为应用程序资源或自定义 Style
的默认 Button
的一部分。
以下示例显示如何切换
Button.Content
值。您可以修改触发器以切换任何其他依赖项属性(例如图像源)。目前尚不清楚您的按钮是什么样子,因此我决定显示针对 Content
属性的触发器:
<Button Command="{Binding SomeCommand}">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Content"
Value="Not running" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Command.IsExecuting}"
Value="True">
<Setter Property="Content"
Value="Command is Running..." />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
关于 ICommand 实现的注释:您的命令既不会将
ICommand.CanExecuteChanged
事件链接到 CommandManager.RequerySuggested
事件,也不能让客户端手动引发它。目前该事件仅在命令执行后才会引发,这是毫无意义的。
当您不将其公开以将其添加到命令 API 时,
ExecuteAsync
的意义何在?
如果您想将
ExecuteAsync
设为虚拟以允许继承者覆盖它,您应该考虑向此虚拟方法添加执行逻辑,以使继承者能够更改行为,而不是仅添加行为。或者,定义 Icommand.Execute
实现也是虚拟的。
通过添加
CanExecute
条件来内部修改 IsExecuting
条件也是值得怀疑的,因为它消除了类型使用方式的一些灵活性。
您的代码包含拼写错误:RisePropertyChanged 而不是 RaisePropertyChanged。
您的
AsyncCommand
的建议改进版本:
public class AsyncCommand : ViewModelBase, ICommand
{
protected readonly Func<Task> _execute;
protected readonly Func<bool>? _canExecute;
private bool _isExecuting;
public bool IsExecuting
{
get => _isExecuting;
set => RisePropertyChanged(ref _isExecuting, value);
}
public bool IsCommandMangerRequerySuggestedEnabled { get; set; }
public AsyncCommand(Func<Task> execute, Func<bool>? canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
this.IsCommandMangerRequerySuggestedEnabled = true;
CommandManager.RequerySuggested += OnCommandManagerRequerySuggested;
}
private void OnCommandManagerRequerySuggested(object? sender, EventArgs e)
{
if (this.IsCommandMangerRequerySuggestedEnabled)
{
this.CanExecuteChanged?.Invoke(sender, e);
}
}
// Manually raise the CanExecuteChanged event
public void InvalidateCommand()
=> this.CanExecuteChanged?.Invoke(this, EventArgs.Empty);
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter) => !_isExecuting && (_canExecute?.Invoke() ?? true);
public async virtual void Execute(object? parameter)
{
if (CanExecute(parameter))
{
try
{
IsExecuting = true;
await ExecuteAsync(parameter);
}
finally
{
IsExecuting = false;
}
}
}
public virtual async Task ExecuteAsync(object? parameter) => await _execute();
protected virtual void OnCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}