如何使用异步命令MVVM WPF

时间:2018-08-07 19:01:36

标签: c# .net wpf mvvm

因此,我最近开始使用MVVM,最终开始了解这个概念。 我似乎偶然发现了命令的一个小问题。 我创建了一个包含按钮和文本框的项目,计划是单击该按钮,然后为该文本框生成一些文本。 当然使用MVVM。 因此,我设法使其正常工作,唯一的问题是,我的按钮绑定了命令,而没有,当单击按钮时,UI冻结了,这是很明显的原因,但这使我思考。为了防止UI死锁,您通常会使用async&await,但与此同时,它开始在Execute();的Command中开始抱怨未等待的方法。 解决这种情况下的UI死锁的正确方法是什么? 我会做一个AsyncCommand还是..? 另外,如果我需要一个AsyncCommand,创建它的正确方法是什么?

查看

<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
    <Button Height="50"
            Width="150"
            Content="Add Product"
            VerticalAlignment="Center"
            HorizontalAlignment="Center"
            Command="{Binding AddProductCommand}"/>

    <TextBox Width="200"
             Margin="0,10,0,0"
             Text="{Binding MyProduct.ProductName}"/>

</StackPanel>

型号

public class ProductModel : INotifyPropertyChanged
    {
        private string _productName;
        private double _productPrice;


        public double ProductPrice
        {
            get { return _productPrice; }
            set
            {
                _productPrice = value;
                OnPropertyChanged("ProductPrice");
            }
        }

        public string ProductName
        {
            get { return _productName; }
            set
            {
                _productName = value;
                OnPropertyChanged("ProductName");
            }
        }


        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

ViewModel

public class ViewModel : INotifyPropertyChanged
    {
        private ProductService _productService;
        private ProductModel _myProduct;
        public AddProductCommand AddProductCommand { get; set; }



        public ViewModel()
        {
            _productService = new ProductService();
            _myProduct = new ProductModel();
            AddProductCommand = new AddProductCommand(this);

        }

        //Bind a button to a command that invokes this method.
        public void FillDescription()
        {
            _myProduct.ProductName = _productService.GetProductName();
        }

        public ProductModel MyProduct
        {
            get { return _myProduct; }
            set
            {
                _myProduct = value;
                OnPropertyChanged("MyProduct");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

服务

public class ProductService
{
    public ProductService()
    {

    }

    public string GetProductName()
    {
        var web = new HtmlWeb();
        var doc = web.Load("https://shop.supersimpleonline.com/products/baby-shark-official-plush");
        var title = doc.DocumentNode.SelectNodes("//h1[@itemprop = 'name']").FirstOrDefault(x => x != null).InnerText;
        return title;
    }
}

命令

public class AddProductCommand : ICommand
{
    public ViewModel ViewModel { get; set; }
    public AddProductCommand(ViewModel viewModel)
    {
        ViewModel = viewModel;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        ViewModel.FillDescription();
    }

    public event EventHandler CanExecuteChanged;
}

2 个答案:

答案 0 :(得分:1)

创建一个通用命令类,该类可以接受要在执行命令时调用的Action<object>

public class DelegateCommand : System.Windows.Input.ICommand
{
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;

    public DelegateCommand(Action<object> execute)
        : this(execute, null)
    {
        _execute = execute;
    }

    public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute == null)
            return true;

        return _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public event EventHandler CanExecuteChanged;
}

然后,您可以传入所需的任何Action<object>,包括在后台线程上调用服务方法的方法,例如:

public class ViewModel : INotifyPropertyChanged
{
    public DelegateCommand AddProductCommand { get; set; }

    public ViewModel()
    {
        _productService = new ProductService();
        _myProduct = new ProductModel();
        AddProductCommand = new DelegateCommand(FillDescription);
    }

    async void FillDescription(object _)
    {
        try
        {
            await Task.Run(() => _myProduct.ProductName = _productService.GetProductName());
        }
        catch(Exception)
        {
            //...
        }
    }
}

答案 1 :(得分:0)

这是一个中继命令,它允许您执行异步工作并防止多次触发该命令/还显示按钮已禁用的ETC。非常简单。

public class AsyncRelayCommand : ICommand
{
    public Func<object, Task> ExecuteFunction { get; }
    public Predicate<object> CanExecutePredicate { get; }
    public event EventHandler CanExecuteChanged;
    public void UpdateCanExecute() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    public bool IsWorking { get; private set; }

    public AsyncRelayCommand(Func<object, Task> executeFunction) : this(executeFunction, (obj) => true) { }
    public AsyncRelayCommand(Func<object, Task> executeFunction, Predicate<object> canExecutePredicate)
    {
        ExecuteFunction = executeFunction;
        CanExecutePredicate = canExecutePredicate;
    }

    public bool CanExecute(object parameter) => !IsWorking && (CanExecutePredicate?.Invoke(parameter) ?? true);
    public async void Execute(object parameter)
    {
        IsWorking = true;
        UpdateCanExecute();

        await ExecuteFunction(parameter);

        IsWorking = false;
        UpdateCanExecute();
    }
}

如果需要,ViewModel还可以使用IsWorking上的AsyncRelayCommand属性来帮助View提供其他工作逻辑。

public class ViewModel : INotifyPropertyChanged
{
    private bool asyncCommandWorking;
    public event PropertyChangedEventHandler PropertyChanged;
    public void Notify([CallerMemberName] string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    public ViewModel()
    {
        AsyncCommand = new AsyncRelayCommand(Execute, CanExecute);
    }

    private Task Execute(object obj)
    {
        return Task.Run(() =>
        {
            // do some work...
        });
    }

    private bool CanExecute(object obj)
    {
        AsyncCommandWorking = AsyncCommand.IsWorking;
        // process other can execute logic.
        // return the result of CanExecute or not
    }

    public AsyncRelayCommand AsyncCommand { get; }
    public bool AsyncCommandWorking
    {
        get => asyncCommandWorking;
        private set
        {
            asyncCommandWorking = value;
            Notify();
        }
    }
}

希望这会有所帮助:)