不仅仅是一个简单的自定义按钮

问题描述 投票:0回答:1

如何创建一个自定义按钮,其中包含根据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,但我已经没有想法了。

我找了很多地方,尝试了很多方法来实现它,但没有成功。

c# wpf
1个回答
0
投票

您可以使用

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);
}
© www.soinside.com 2019 - 2024. All rights reserved.