如何创建和使用异步命令抽象类?

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

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

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

这是为了防止酱汁聚集在一处。

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

有什么解决办法吗?

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

<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();

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); }

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;
    }
}



*插入await延迟是为了防止调试警告。

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);
}

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

谢谢。 :)

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

所以我无法应用它...

c# wpf asynchronous mvvm command
2个回答
1
投票

我的基类实现示例(.Net 8): 基地Inpc, RelayCommand、RelayCommandAsync、 RelayCommand、RelayCommandAsync

代码中有很多详细的文档标签。

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] in string? propertyName = null)
        {
            PropertyChangedEventHandler? propertyChanged = PropertyChanged;
            if (propertyChanged is not null)
            {
                PropertyChangedEventArgs args = string.IsNullOrEmpty(propertyName)
                                ? allProperties
                                : new PropertyChangedEventArgs(propertyName);
                propertyChanged(this, args);
            }
        }

        private static readonly PropertyChangedEventArgs allProperties = new PropertyChangedEventArgs(string.Empty);

        /// <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>
        /// <param name="isAlways">If <see langword="true"/>, then the assignment to the field,
        /// the raising of the <see cref="PropertyChanged"/> event and
        /// the calling of the <see cref="OnPropertyChanged(in string, in object?, in object?)"/> method
        /// always occur, regardless of the comparison of the new value with the old one.</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, in bool isAlways = false, [CallerMemberName] in string? propertyName = null)
        {
            ArgumentException.ThrowIfNullOrWhiteSpace(propertyName);
            if (isAlways || !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) { }
    }
}
namespace Simplified
{
    // Delegates for WPF Command Methods
    public delegate void ExecuteHandler();
    public delegate bool CanExecuteHandler();

    public delegate void ExecuteHandler<T>(T parameter);
    public delegate bool CanExecuteHandler<T>(T parameter);

    public delegate bool ConverterFromObjectHandler<T>(in object value, out T result);
}
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)
        {
            ArgumentNullException.ThrowIfNull(execute);
            ArgumentNullException.ThrowIfNull(canExecute);
            this.execute = execute;
            this.canExecute = canExecute;

            requerySuggested = (o, e) => Invalidate();
            CommandManager.RequerySuggested += requerySuggested;
        }

        /// <inheritdoc cref="RelayCommand(ExecuteHandler{object?}, CanExecuteHandler{object?})"/>
        public RelayCommand(ExecuteHandler<object?> execute)
            : this(execute, _ => true)
        { }

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

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

        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 void Invalidate()
            => CanExecuteChanged?.Invoke(this, EventArgs.Empty);


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

        /// <inheritdoc cref="ICommand.Execute(object)"/>
        public void Execute(object? parameter)
        {
            execute(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;
                }
            }
        }

    }

}

有些课程与答案不符。 把它们从这里带走。

在代码中使用 RelayCommand 的示例。

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 属性及其所需的其他成员创建一个方法。

但很大程度上,这一切(同步执行方法除外)都可以封装在命令本身中。

封装执行方法是没有意义的,因为正是因为它,命令实例才彼此不同。

我使用异步命令的示例

在示例中,闪烁的背景是为了了解 GUI 滞后发生的时刻 - 滞后期间闪烁停止。

还有几个按钮可以演示同步和异步方法执行之间的区别,以及其中的错误和异常如何影响应用程序。
单击它们的各种组合。

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

如果您在测试过程中有任何疑问 - 请询问。

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>
    

0
投票
我正在使用 James Montemagno 的 MVVM-Helpers 库中的

AsyncCommand

 实现(目前也在 Xamarin 社区工具包中实现),您可以在此处查看实现:
AsyncCommand 实现

你可以这样使用它:

安装

Refactored.MvvmHelpers nuget 包。

MyViewModel.cs

public class MyViewModel { //other code goes here (Models collection, IsBusy, etc) public ICommand DoWorkCommand => new AsyncCommand(DoWorkAsync); public ICommand DoWorkWithParameterCommand => new AsyncCommand<bool>(DoWorkWithParameterAsync); private async Task DoWorkAsync(){ //simulate work await Task.Delay(1000); } private async Task DoWorkWithParameterAsync(bool itemExists){ //simulate work await Task.Delay(1000); } }
MyView.cs

<BusyIndicator IsBusy="{IsBusy, mode=TwoWay}"> <Listbox ItemsSource="{Binding Models}"/> <Button Command="{Binding DoWorkCommand}" /> <Button Command="{Binding DoWorkWithParameterCommand}" CommandParameter="False"/> </BusyIndicator>
    
© www.soinside.com 2019 - 2024. All rights reserved.