因此,我最近开始使用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;
}
答案 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();
}
}
}
希望这会有所帮助:)