我正在使用 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);
}
如果我的代码很奇怪或者有更好的代码,请告诉我!
谢谢。 :)
我看过很多这个网站和其他示例,但这是一个使用典型函数的示例,
所以我无法应用它...
代码中有很多详细的文档标签。
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 属性及其所需的其他成员创建一个方法。但很大程度上,这一切(同步执行方法除外)都可以封装在命令本身中。
封装执行方法是没有意义的,因为正是因为它,命令实例才彼此不同。
还有几个按钮可以演示同步和异步方法执行之间的区别,以及其中的错误和异常如何影响应用程序。
单击它们的各种组合。
如果您在测试过程中有任何疑问 - 请询问。
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="	"/>
<Run Text="HasErrors:"/>
<Run Text="{Binding TestCommandAsync.HasErrors, Mode=OneWay}"/>
<Run Text="	"/>
<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>
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>