本文介绍了如何创建和使用异步命令抽象类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 wpf mvvm 开发一些东西.

I am developing something using wpf mvvm.

目前,我的实现风格有点不寻常.

Currently, my implementation style is a bit unusual.

这是为了防止酱汁聚集在一个地方.

This is to prevent the sauce from gathering in one place.

应该避免 Async void,但我不知道如何避免.

Async void should be avoided, but I don't know how to avoid it.

有什么解决办法吗?

以下是我使用的来源.(RelayCommand 是通用的,我需要创建一个新的异步命令.)

Below is the source I used. (RelayCommand is generic and I need to create a new async command.)

<View.xaml>, <View.xaml.cs>

<BusyIndicator IsBusy="{IsBusy, mode=TwoWay}">
    <Listbox ItemsSource="{Binding Models}"/>
    <Button Command="{Binding OnClickButtonCommand}"
            CommandParameter="whether the value(string) exists or not"/>
</BusyIndicator>

this.DataContext = new ViewModel();

<ViewModel.cs>

public ViewModel() { }

private isBusy;
public isBusy
{
    get => isBusy;
    set => SetField(ref isBusy, value);
}

public ObservableCollection<Model> Models { get; set; }

public ICommand OnClickButtonCommand { get => new ButtonCommand(this); }

<ButtonCommand.cs>

public ButtonCommand : RelayCommand
{
    private ViewModel viewModel;
    public ViewModel(ViewModel viewModel)
    {
        this.viewModel = viewModel
    }

    public override async void Execute(object obj)
    {
        viewModel.IsBusy = true;
        await Task.Delay(1500);

        viewModel.Models = new ObservableCollection<Model>(await Data());
        viewModel.IsBusy = false;
    }

    private async Task<List<Model>> Data()
    {
        await Task.Delay(100);
        var data = new List<Model>();
              ...
        return data;
    }
}



*插入等待延迟是为了防止调试警告.

*Inserting await delay is to prevent debugging warning.

<CommandBase.cs>

public interface IRelayCommand : ICommand
{
    new void Execute(object obj);
}

public abstract class RelayCommand : IRelayCommand
{
    public RelayCommand() { }

    public event EventHandler CanExecuteChanged
    {
        add    => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    bool ICommand.CanExecute(object obj)
    {
        if (obj?.ToString().Length == 0)
        {
            return false;
        }
        else
        {
            return true;
        }
        //return canExecute == null || this.canExecute.Invoke(parameter);
    }
    public abstract void Execute(object obj);
}

如果我的代码很奇怪或者有更好的代码,请告诉我!

And if my code is weird or there is better code, please let me know!

谢谢.:)

我看过很多这个网站和其他示例,但这是一个使用典型功能的示例,

I've seen a lot of this site and other samples, but it's an example using a typical fuction,

所以我不能应用它...

so I can't apply it...

推荐答案

我的基类实现示例:基本信息,RelayCommand, RelayCommandAsync,RelayCommand、RelayCommandAsync.

代码中有很多冗长的文档标签.

An example of my implementation of base classes:BaseInpc,RelayCommand, RelayCommandAsync,RelayCommand<T>, RelayCommandAsync<T>.

There are a lot of verbose documentation tags in the code.

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Simplified
{
    /// <summary>Base class with implementation of the <see cref="INotifyPropertyChanged"/> interface.</summary>
    public abstract class BaseInpc : INotifyPropertyChanged
    {
        /// <inheritdoc cref="INotifyPropertyChanged"/>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>The protected method for raising the event <see cref = "PropertyChanged"/>.</summary>
        /// <param name="propertyName">The name of the changed property.
        /// If the value is not specified, the name of the method in which the call was made is used.</param>
        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary> Protected method for assigning a value to a field and raising 
        /// an event <see cref = "PropertyChanged" />. </summary>
        /// <typeparam name = "T"> The type of the field and assigned value. </typeparam>
        /// <param name = "propertyFiled"> Field reference. </param>
        /// <param name = "newValue"> The value to assign. </param>
        /// <param name = "propertyName"> The name of the changed property.
        /// If no value is specified, then the name of the method 
        /// in which the call was made is used. </param>
        /// <remarks> The method is intended for use in the property setter. <br/>
        /// To check for changes,
        /// used the <see cref = "object.Equals (object, object)" /> method.
        /// If the assigned value is not equivalent to the field value,
        /// then it is assigned to the field. <br/>
        /// After the assignment, an event is created <see cref = "PropertyChanged" />
        /// by calling the method <see cref = "RaisePropertyChanged (string)" />
        /// passing the parameter <paramref name = "propertyName" />. <br/>
        /// After the event is created,
        /// the <see cref = "OnPropertyChanged (string, object, object)" />
        /// method is called. </remarks>
        protected void Set<T>(ref T propertyFiled, in T newValue, [CallerMemberName] in string propertyName = null)
        {
            if (!Equals(propertyFiled, newValue))
            {
                T oldValue = propertyFiled;
                propertyFiled = newValue;
                RaisePropertyChanged(propertyName);

                OnPropertyChanged(propertyName, oldValue, newValue);
            }
        }

        /// <summary> The protected virtual method is called after the property has been assigned a value and after the event is raised <see cref = "PropertyChanged" />. </summary>
        /// <param name = "propertyName"> The name of the changed property. </param>
        /// <param name = "oldValue"> The old value of the property. </param>
        /// <param name = "newValue"> The new value of the property. </param>
        /// <remarks> Can be overridden in derived classes to respond to property value changes. <br/>
        /// It is recommended to call the base method as the first operator in the overridden method. <br/>
        /// If the overridden method does not call the base class, then an unwanted change in the base class logic is possible. </remarks>
        protected virtual void OnPropertyChanged(in string propertyName, in object oldValue, in object newValue) { }
    }
}
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;

namespace Simplified
{
    /// <summary> A class that implements <see cref = "ICommand" />. <br/>
    /// Implementation taken from <see href="https://www.cyberforum.ru/wpf-silverlight/thread2390714-page4.html#post13535649"/>
    /// and added a constructor for methods without a parameter.</summary>
    public class RelayCommand : ICommand
    {
        protected readonly CanExecuteHandler<object> canExecute;
        protected readonly ExecuteHandler<object> execute;
        private readonly EventHandler requerySuggested;

        /// <inheritdoc cref="ICommand.CanExecuteChanged"/>
        public event EventHandler CanExecuteChanged;

        /// <summary> Command constructor. </summary>
        /// <param name = "execute"> Command method to execute. </param>
        /// <param name = "canExecute"> Method that returns the state of the command. </param>
        public RelayCommand(ExecuteHandler<object> execute, CanExecuteHandler<object> canExecute = null)
           : this()
        {
            this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
            this.canExecute = canExecute;
        }

        /// <inheritdoc cref="RelayCommand(ExecuteHandler{object}, CanExecuteHandler{object})"/>
        public RelayCommand(ExecuteHandler execute, CanExecuteHandler canExecute = null)
                : this
                (
                      p => execute(),
                      p => canExecute?.Invoke() ?? true
                )
        { }

        private readonly Dispatcher dispatcher = Application.Current.Dispatcher;

        /// <summary> The method that raises the event <see cref="CanExecuteChanged"/>.</summary>
        public void RaiseCanExecuteChanged()
        {
            if (dispatcher.CheckAccess())
            {
                invalidate();
            }
            else
            {
                _ = dispatcher.BeginInvoke(invalidate);
            }
        }
        private readonly Action invalidate;
        private RelayCommand()
        {
            invalidate = () => CanExecuteChanged?.Invoke(this, EventArgs.Empty);

            requerySuggested = (o, e) => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
            CommandManager.RequerySuggested += requerySuggested;
        }

        /// <inheritdoc cref="ICommand.CanExecute(object)"/>
        public bool CanExecute(object parameter)
        {
            return canExecute?.Invoke(parameter) ?? true;
        }

        /// <inheritdoc cref="ICommand.Execute(object)"/>
        public void Execute(object parameter)
        {
            execute?.Invoke(parameter);
        }

    }


}
using System;
using System.Collections;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;

namespace Simplified
{
    /// <summary>A class that implements asynchronous commands with the execution
    /// of the <see cref="RelayCommand.Execute(object)"/>  method in Task. </summary>
    /// <remarks>Only one call to the <see cref="RelayCommand.Execute(object)"/>
    /// method is allowed at a time.
    /// During the execution of the method, the <see cref="IsBusy"/> flag is set and
    /// the <see cref="RelayCommand.CanExecute(object)"/> method of the command will return false.
    /// The <see cref="INotifyDataErrorInfo"/> interface is implemented to notify about
    /// an erroneous call to the <see cref="RelayCommand.Execute(object)"/> method before
    /// the previous execution completes and about exceptions during the
    /// execution of <see cref="RelayCommand.Execute(object)"/>.
    /// To notify about changes in property values, the <see cref="INotifyPropertyChanged"/>
    /// interface is implemented.</remarks>
    public class RelayCommandAsync : RelayCommand, ICommand, INotifyPropertyChanged, INotifyDataErrorInfo
    {
        /// <inheritdoc cref="INotifyPropertyChanged.PropertyChanged"/>
        public event PropertyChangedEventHandler PropertyChanged;


        /// <inheritdoc cref="INotifyDataErrorInfo.ErrorsChanged"/>
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        /// <summary>The command is in the execution state of the <see cref="RelayCommand.Execute(object)"/> method.</summary>
        public bool IsBusy { get; private set; }

        /// <inheritdoc cref="INotifyDataErrorInfo.HasErrors"/>
        public bool HasErrors { get; private set; }

        /// <summary>Exception from the last execution of the <see cref="RelayCommand.Execute(object)"/> method.</summary>
        public Exception ExecuteException { get; private set; }

        // A flag indicating a "call to execute busy a command" error.
        private bool isBusyExecuteError;

        /// <summary>Sets a value to the <see cref="IsBisy"/> property and notifies of its change.</summary>
        /// <param name="isBusy">The value for the property.</param>
        protected void SetIsBusy(bool isBusy)
        {
            if (IsBusy != isBusy)
            {
                IsBusy = isBusy;
                PropertyChanged?.Invoke(this, Args.IsBusyPropertyEventArgs);
                RaiseCanExecuteChanged();
            }
        }

        /// <summary>Sets the HasErrors property and reports an entity-level error.</summary>
        /// <param name="hasErrors">The value for the property.</param>
        protected void SetEntityHasErrors(bool hasErrors)
              => SetHasErrors(hasErrors, Args.EntityLevelErrorsEventArgs);

        /// <summary>Sets the HasErrors property and reports an entity or property level error.</summary>
        /// <param name="hasErrors">The value for the property.</param>
        /// <param name="args">Argument with data about the error level.</param>
        protected void SetHasErrors(bool hasErrors, DataErrorsChangedEventArgs args)
        {
            if (HasErrors != hasErrors)
            {
                HasErrors = hasErrors;
                PropertyChanged?.Invoke(this, Args.HasErrorsPropertyEventArgs);
            }
            ErrorsChanged?.Invoke(this, args);
        }


        /// <summary>Sets a value to the <see cref="ExecuteException"/> property and notifies of its change.</summary>
        /// <param name="exception">The value for the property.</param>
        protected void SetExecuteException(Exception exception)
        {
            if (ExecuteException != exception)
            {
                ExecuteException = exception;
                PropertyChanged?.Invoke(this, Args.ExecuteExceptionPropertyEventArgs);
            }
        }

        /// <inheritdoc cref="RelayCommand(ExecuteHandler{object}, CanExecuteHandler{object})"/>
        public RelayCommandAsync(ExecuteHandler<object> execute, CanExecuteHandler<object> canExecute = null)
            : this(new AsyncData(execute, canExecute))
        { }

        /// <inheritdoc cref="RelayCommand(ExecuteHandler, CanExecuteHandler)"/>
        public RelayCommandAsync(ExecuteHandler execute, CanExecuteHandler canExecute = null)
            : this(new AsyncData(execute, canExecute))
        { }

        // The field for storing additional, auxiliary data generated
        // during the generation of the asynchronous method, wrapping
        // the one obtained in the constructor.
        private readonly AsyncData data;

        /// <inheritdoc cref="RelayCommand(ExecuteHandler{object}, CanExecuteHandler{object})"/>
        protected RelayCommandAsync(AsyncData data)
            : base(data.ExecuteAsync, data.CanExecuteAsync)
        {
            this.data = data;
            this.data.commandAsync = this;
        }

        /// <inheritdoc cref="INotifyDataErrorInfo.GetErrors(string)"/>
        public IEnumerable GetErrors(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                if (isBusyExecuteError)
                {
                    yield return Args.BusyExecuteErrorMessage;
                }

                if (ExecuteException != null)
                {
                    yield return ExecuteException;
                }
            }
            IEnumerable errors = GetErrorsOverride(propertyName);
            if (errors != null)
            {
                foreach (var error in errors)
                {
                    yield return error;
                }
            }
        }

        /// <summary>Method overridden in derived classes to add error information</summary>
        /// <param name="propertyName">The name of the property to retrieve validation
        /// errors for; or null or Empty, to retrieve entity-level errors.</param>
        /// <returns>The validation errors for the property or entity.</returns>
        protected virtual IEnumerable GetErrorsOverride(string propertyName)
            => null;

        /// <summary>A class with persistent elements to avoid re-creating them frequently.</summary>
        public static class Args
        {
            public const string BusyExecuteErrorMessage = "Called the execution of a command when it is busy.";

            public static readonly PropertyChangedEventArgs IsBusyPropertyEventArgs = new PropertyChangedEventArgs(nameof(IsBusy));
            public static readonly PropertyChangedEventArgs HasErrorsPropertyEventArgs = new PropertyChangedEventArgs(nameof(HasErrors));
            public static readonly PropertyChangedEventArgs ExecuteExceptionPropertyEventArgs = new PropertyChangedEventArgs(nameof(ExecuteException));

            public static readonly DataErrorsChangedEventArgs EntityLevelErrorsEventArgs = new DataErrorsChangedEventArgs(string.Empty);
        }

        /// <summary>A class for storing additional, auxiliary data and methods that are generated
        /// when generating asynchronous methods that wrap the synchronous methods received
        /// in the constructor.</summary>
        protected class AsyncData
        {
            public RelayCommandAsync commandAsync;
            public async void ExecuteAsync(object parameter)
            {
                if (commandAsync.IsBusy)
                {
                    commandAsync.isBusyExecuteError = true;
                    commandAsync.SetEntityHasErrors(true);
                }
                else
                {
                    commandAsync.SetIsBusy(true);

                    try
                    {
                        await Task.Run(() => execute(parameter));

                        commandAsync.isBusyExecuteError = false;
                        commandAsync.SetExecuteException(null);
                        commandAsync.SetEntityHasErrors(false);
                    }
                    catch (Exception ex)
                    {
                        commandAsync.SetExecuteException(ex);
                        commandAsync.SetEntityHasErrors(true);
                    }
                    finally
                    {
                        commandAsync.SetIsBusy(false);
                    }
                }
            }

            public CanExecuteHandler<object> CanExecuteAsync { get; }
            private bool canExecuteNullAsync(object parameter) => !commandAsync.IsBusy;
            private bool canExecuteAsync(object parameter) => !commandAsync.IsBusy && canExecute(parameter);

            private readonly ExecuteHandler<object> execute;
            private readonly CanExecuteHandler<object> canExecute;

            /// <inheritdoc cref="AsyncData(ExecuteHandler, CanExecuteHandler)"/>
            public AsyncData(ExecuteHandler<object> execute, CanExecuteHandler<object> canExecute)
            {
                this.execute = execute ?? throw new ArgumentNullException(nameof(execute));


                if (canExecute == null)
                {
                    CanExecuteAsync = canExecuteNullAsync;
                }
                else
                {
                    this.canExecute = canExecute;
                    CanExecuteAsync = canExecuteAsync;
                }
            }

            /// <summary>Creates an instance.</summary>
            /// <param name="execute">Synchronous Execute method.</param>
            /// <param name="canExecute">Synchronous CanExecute method.</param>
            public AsyncData(ExecuteHandler execute, CanExecuteHandler canExecute)
            {
                if (execute == null)
                {
                    throw new ArgumentNullException(nameof(execute));
                }

                this.execute = p => execute();


                if (canExecute == null)
                {
                    CanExecuteAsync = canExecuteNullAsync;
                }
                else
                {
                    this.canExecute = p => canExecute();
                    CanExecuteAsync = canExecuteAsync;
                }
            }
        }

    }

}

有些课程不适合答案.从这里.

Some of the classes did not fit into the answer.Take them from here.

    public class ViewModel : BaseInpc
    {

        private bool _isBusy;
        public bool IsBusy
        {
            get => _isBusy;
            set => Set(ref _isBusy, value);
        }

        public ObservableCollection<Model> Models { get; set; }

        // Under no circumstances should you get an instance of a command in this way.
        // As such, each time the property is accessed, a NEW instance of the command is created.
        // As a result, in each element of the Window, in the ViewMode, etc. there are different instances everywhere.
        // This is a very common cause of various bugs.
        //
        //public ICommand OnClickButtonCommand { get => new ButtonCommand(this); }

        // One of the options for the correct team creation.
        private RelayCommand _onClickButtonCommand;
        public RelayCommand OnClickButtonCommand => _onClickButtonCommand
            ?? (_onClickButtonCommand = new RelayCommand(ExecuteOnClick));

        // Executing command method
        private async void ExecuteOnClick(object parameter)
        {
            IsBusy = true;
            await Task.Delay(1500);

            Models = new ObservableCollection<Model>(await Data());
            IsBusy = false;
        }
        private async Task<List<Model>> Data()
        {
            await Task.Delay(100);
            var data = new List<Model>();

            // Some code

            return data;
        }

    }

在这样的实现中,您可以为每个命令、它自己的 IsBusy 属性和它需要的其他成员创建一个方法.

In such an implementation, you can create a method for each command, its own IsBusy property and other members of which it needs.

但很大程度上,这一切(同步执行方法除外)都可以封装在命令本身中.
封装执行方法没有意义,正因为如此,命令实例才会彼此不同.

But to a large extent, all this (except for the synchronous executing method) can be encapsulated in the command itself.
Encapsulating the execution method does not make sense, since it is because of it that the command instances differ from each other.

在示例中,背景闪烁,以便了解 GUI 滞后发生的时刻 - 在滞后期间闪烁停止.
还有几个按钮来演示同步和异步方法执行之间的区别,以及它们中的错误和异常如何影响应用程序.
以各种组合点击它们.

In the example, a blinking background, in order to understand at what moment the GUI lags occur - blinking during lags stops.
There are also several buttons to demonstrate the difference between synchronous and asynchronous method execution, and how errors and exceptions in them affect applications.
Click them in various combinations.

出现错误和异常时,它们会显示在窗口底部的按钮下方.

When errors and exceptions appear, they are displayed under the buttons at the bottom of the window.

如果您在测试期间有任何问题,请提出.

If you have any questions during testing - ask.

using Simplified;
using System;
using System.Threading;

namespace RelayCommandAsyncTest
{
    public class TestViewModel : BaseInpc
    {
        private int _count;
        // Command execution counter (common for all commands)
        public int Count { get => _count; set => Set(ref _count, value); }

        // Аsynchronous command
        public RelayCommandAsync TestCommandAsync { get; }
        // Synchronous command
        public RelayCommand TestCommand { get; }
        // Synchronous command with Lags
        public RelayCommand TestLagsCommand { get; }
        // Synchronous command executing asynchronous command
        // without checking it IsBusy.
        public RelayCommand TestExecuteCommandAsync { get; }

        // Long running execution method for validating
        // an asynchronous command 
        private void ExecuteForAsync(object obj)
        {
            Thread.Sleep(2000);
            Count++;
            // If the command parameter is not passed,
            // then there will be an exception
            Thread.Sleep(Convert.ToInt32(obj));
        }

        // Long running execution method for creating
        // lags in a synchronous command
        private void ExecuteLags(object obj)
        {
            isExecuteLagsCommand = true;
            TestLagsCommand.RaiseCanExecuteChanged();

            Thread.Sleep(2000);
            Count++;
            // If the command parameter is not passed,
            // then there will be an exception
            Thread.Sleep(Convert.ToInt32(obj));

            isExecuteLagsCommand = false;
            TestLagsCommand.RaiseCanExecuteChanged();
        }
        bool isExecuteLagsCommand;

        // Fast executing method for checking lags with
        // a synchronous command
        private void Execute(object obj)
        {
            Count++;

            // If the command parameter is not passed,
            // then there will be an exception
            Thread.Sleep(Convert.ToInt32(obj));
        }

        // A command that executes an asynchronous command without checking if it is busy.
        // If the asynchronous command is busy, it will generate a validation error.
        private void ExecuteCommandAsync(object obj)
        {
            Count++;
            TestCommandAsync.Execute(obj);
        }
        public TestViewModel()
        {
            TestCommandAsync = new RelayCommandAsync(ExecuteForAsync);
            TestCommand = new RelayCommand(Execute);
            TestLagsCommand = new RelayCommand(ExecuteLags, p => !isExecuteLagsCommand);
            TestExecuteCommandAsync = new RelayCommand(ExecuteCommandAsync);


            timer = new Timer
            (
                _ => TestCommandAsync.RaiseCanExecuteChanged(),
                null,
                0,
                5000
            );
        }

        // Timer to check for raising CanExecuteChanged from any thread.
        private readonly Timer timer;
    }

}
<Window x:Class="RelayCommandAsyncTest.CommandTestWindow"
        x:Name="main"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:RelayCommandAsyncTest" xmlns:common="clr-namespace:Common;assembly=Common"
        mc:Ignorable="d"
        Title="CommandTestWindow" Height="1000" Width="800"
        FontSize="30">
    <FrameworkElement.Triggers>
        <EventTrigger RoutedEvent="Loaded" SourceName="main">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation Storyboard.TargetName="FlashingBrush"
                                    Storyboard.TargetProperty="Color"
                                    Duration="0:0:0.5"
                                    To="LightYellow"
                                    AutoReverse="True"
                                    RepeatBehavior="Forever"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </FrameworkElement.Triggers>
    <FrameworkElement.DataContext>
        <local:TestViewModel/>
    </FrameworkElement.DataContext>
    <FrameworkElement.Resources>
        <ControlTemplate x:Key="validationTemplate">
            <Border BorderThickness="5"
                    BorderBrush="Red">
                <Grid IsHitTestVisible="False">
                    <Viewbox>
                        <TextBlock Foreground="Red" FontSize="80"
                                   Text="! ! !"/>
                    </Viewbox>
                    <AdornedElementPlaceholder/>
                </Grid>
            </Border>
        </ControlTemplate>
        <Style TargetType="Button">
            <Setter Property="Validation.ErrorTemplate" Value="{DynamicResource validationTemplate}"/>
        </Style>
    </FrameworkElement.Resources>
    <Grid>
        <Grid.Background>
            <SolidColorBrush x:Name="FlashingBrush" Color="LightGreen"/>
        </Grid.Background>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto" />
            <RowDefinition/>
        </Grid.RowDefinitions>
        <UniformGrid Columns="2">
            <Button x:Name="button" Content="Async throw Exception"
                    Command="{Binding TestCommandAsync}"
                    CommandParameter="qwerty" Margin="15"/>
            <Button Content="Async Normal"
                    Command="{Binding TestCommandAsync}"
                    CommandParameter="2000" Margin="15"/>
            <Button Content="Sync Lags"
                    Command="{Binding TestLagsCommand}"
                    CommandParameter="2000" Margin="15"/>
            <Button Content="IsBusy is not checked"
                    Command="{Binding TestExecuteCommandAsync}" Margin="15"
                    CommandParameter="0">
                <Button.Style>
                    <Style TargetType="Button">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding TestCommandAsync.HasErrors}"
                                         Value="True">
                                <Setter Property="Foreground" Value="Red"/>
                                <Setter Property="FontWeight" Value="ExtraBold"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Button.Style>
            </Button>
            <Button Content="Sync throw Exception"
                    Command="{Binding TestCommand}"
                    CommandParameter="qwerty" Margin="15"/>
            <Button Content="Sync Normal"
                    Command="{Binding TestCommand}" Margin="15"
                    CommandParameter="2000"/>
        </UniformGrid>
        <TextBlock Grid.Row="1"  Margin="5">
            <Run Text="IsBusy:"/>
            <Run Text="{Binding TestCommandAsync.IsBusy, Mode=OneWay}"/>
            <Run Text="&#9;"/>
                <Run Text="HasErrors:"/>
            <Run Text="{Binding TestCommandAsync.HasErrors, Mode=OneWay}"/>
            <Run Text="&#9;"/>
            <Run Text="Count:"/>
            <Run Text="{Binding Count}"/>
        </TextBlock>
        <ScrollViewer Grid.Row="2"
                      HorizontalScrollBarVisibility="Auto"
                      VerticalScrollBarVisibility="Auto">
            <ItemsControl ItemsSource="{Binding ElementName=button, Path=(Validation.Errors)}"
                      DisplayMemberPath="ErrorContent"
                      FontSize="15"/>
        </ScrollViewer>
    </Grid>
</Window>

这篇关于如何创建和使用异步命令抽象类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-26 05:05