我刚刚开始用C#编程,并希望从一开始就做任何事情。 所以我了解了MVVM模式,并试图使我的程序与它一起工作。 为此我使用了ICommand的Delegate和lambda函数。
这很好用,但由于我的程序使用HTML请求,我必须找到一种方法来调用命令异步。
所以我找到了一个很好的实现,我试图理解,但我一度失败了......实现的是:
public interface IRaiseCanExecuteChanged
{
void RaiseCanExecuteChanged();
}
// And an extension method to make it easy to raise changed events
public static class CommandExtensions
{
public static void RaiseCanExecuteChanged(this ICommand command)
{
var canExecuteChanged = command as IRaiseCanExecuteChanged;
if (canExecuteChanged != null)
canExecuteChanged.RaiseCanExecuteChanged();
}
}
public class DelegateCommand : DelegateCommand<object>
{
public DelegateCommand(Action executeMethod)
: base(o => executeMethod())
{
}
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
: base(o => executeMethod(), o => canExecuteMethod())
{
}
}
/// <summary>
/// A command that calls the specified delegate when the command is executed.
/// </summary>
/// <typeparam name="T"></typeparam>
public class DelegateCommand<T> : ICommand, IRaiseCanExecuteChanged
{
private readonly Func<T, bool> _canExecuteMethod;
private readonly Action<T> _executeMethod;
private bool _isExecuting;
public DelegateCommand(Action<T> executeMethod)
: this(executeMethod, null)
{
}
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
{
if ((executeMethod == null) && (canExecuteMethod == null))
{
throw new ArgumentNullException("executeMethod", @"Execute Method cannot be null");
}
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
bool ICommand.CanExecute(object parameter)
{
return !_isExecuting && CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
_isExecuting = true;
try
{
RaiseCanExecuteChanged();
Execute((T)parameter);
}
finally
{
_isExecuting = false;
RaiseCanExecuteChanged();
}
}
public bool CanExecute(T parameter)
{
if (_canExecuteMethod == null)
return true;
return _canExecuteMethod(parameter);
}
public void Execute(T parameter)
{
_executeMethod(parameter);
}
}
public interface IAsyncCommand : IAsyncCommand<object>
{
}
public interface IAsyncCommand<in T> : IRaiseCanExecuteChanged
{
Task ExecuteAsync(T obj);
bool CanExecute(object obj);
ICommand Command { get; }
}
public class AwaitableDelegateCommand : AwaitableDelegateCommand<object>, IAsyncCommand
{
public AwaitableDelegateCommand(Func<Task> executeMethod)
: base(o => executeMethod())
{
}
public AwaitableDelegateCommand(Func<Task> executeMethod, Func<bool> canExecuteMethod)
: base(o => executeMethod(), o => canExecuteMethod())
{
}
}
public class AwaitableDelegateCommand<T> : IAsyncCommand<T>, ICommand
{
private readonly Func<T, Task> _executeMethod;
private readonly DelegateCommand<T> _underlyingCommand;
private bool _isExecuting;
public AwaitableDelegateCommand(Func<T, Task> executeMethod)
: this(executeMethod, _ => true)
{
}
public AwaitableDelegateCommand(Func<T, Task> executeMethod, Func<T, bool> canExecuteMethod)
{
_executeMethod = executeMethod;
_underlyingCommand = new DelegateCommand<T>(x => { }, canExecuteMethod);
}
public async Task ExecuteAsync(T obj)
{
try
{
_isExecuting = true;
RaiseCanExecuteChanged();
await _executeMethod(obj);
}
finally
{
_isExecuting = false;
RaiseCanExecuteChanged();
}
}
public ICommand Command { get { return this; } }
public bool CanExecute(object parameter)
{
return !_isExecuting && _underlyingCommand.CanExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { _underlyingCommand.CanExecuteChanged += value; }
remove { _underlyingCommand.CanExecuteChanged -= value; }
}
public async void Execute(object parameter)
{
await ExecuteAsync((T)parameter);
}
public void RaiseCanExecuteChanged()
{
_underlyingCommand.RaiseCanExecuteChanged();
}
}
现在我有两个问题。 1.像他们一样实施它是一个好人吗?正如我所看到的,对象T仅用作函数参数,我可以传递给我的lambda函数。
我可以使用此代码执行以下操作:
private ObservableList<string, string> dict;
private IAsyncCommand searchCommand;
public async Task myFunction() {
//changes global variable dict bound to view
public IAsyncCommand MyCommand
{
get
{
if (myCommand == null)
{
myCommand = new AwaitableDelegateCommand(
() =>
{
return myFunction(myParameter);
});
}
return searchCommand;
}
}
其中myFunction编辑一个全局变量dict,它通过函数MyFunction与getters和setter绑定到视图。我希望这是常见的做法,因为我就是这样发现的。 我们在这里返回任务,AwaitableDelegateCommand在内部等待它,所以我们不必关心。但我们无法访问任务及其结果,对吧?
那么如果myFunction不返回void而是变量,我该如何实现呢? 然后我将得到任务的结果(等待异步后)并将其分配给列表。
代码看起来像这样:
public async Task<ObservableDictionary<string, string>> myFunction() {
var dict = new ObservableDictionary<string, string>();
//do work....
return dict;
}
public IAsyncCommand MyCommand
{
get
{
if (myCommand == null)
{
//what to do here to assign dict the result of the Task?
}
return searchCommand;
}
}
这将使我的代码更可重用,我希望它是好的:) 希望有人可以帮助我。
编辑: 由于我的问题似乎不太清楚,我试着再解释一下。
我的方法返回一个值,我希望我的方法与AwaitableDelegateCommand
运行异步现在问题...一个返回值的方法通常对代码的其余部分没有任何作用,但是返回的值... 在我的例子中,它从HTML请求填充字典并返回它。 如果我现在在lambda中运行方法async,它什么都不做....因为我无法在后台获得Task的结果。 所以我现在通过重写函数来解决这个问题。它不是填充本地字典变量,而是填充全局字典变量。 这是我能够找到访问函数数据的唯一方法。 我只是想知道是否有另一种方法可以做到这一点,或者这是唯一/最佳方式。
希望这更好。
答案 0 :(得分:1)
wpf中的ICommand
绑定是即发即弃的,也就是说,它们没有直接的返回值。命令的效果通过使用INotifyPropertyChanged
:
internal class MyViewModel : INotifyPropertyChanged
{
public MyViewModel( IServer server )
{
MyCommand = new DelegateCommand( async () => MyProperty = await server.GetNewData() );
}
#region Bindings
public string MyProperty
{
get
{
return _myProperty;
}
set
{
_myProperty = value;
OnPropertyChanged();
}
}
public ICommand MyCommand { get; }
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region private
private string _myProperty;
private void OnPropertyChanged( [CallerMemberName] string propertyName = null )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
#endregion
}
public interface IServer
{
Task<string> GetNewData();
}
和MyView
会有类似的内容:
<Button Content="Click me!" Command={Binding MyCommand}" />
<TextBlock Text={Binding MyProperty} />
现在,如果单击该按钮,则会执行MyCommand
,从服务器请求数据并在数据到达后更新MyProperty
。 MyProperty
触发对MyView
TextBlock
的更新,用户会看到新数据......
编辑:
在我的情况下[方法]从HTML请求填充字典并返回它。
因此,该方法无法在命令中作为委托使用(直接)。命令调用的委托必须修改应用程序的状态,否则命令不执行任何操作,正如您所说的那样。因此,您应该创建一个带有委托的命令,该委托调用该方法并使用返回值执行某些操作:
MyCommand = new DelegateCommand( async () => _model.Add( await ParseDataFromRequest() );
话虽如此,ParseDataFromRequest
无论如何都不应该是视图模型的成员,它属于应用程序的逻辑部分。视图模型应该只是转发和调整视图的消费数据。
答案 1 :(得分:0)
由于您要异步检索数据,我建议在Mvvm.Async
library中使用layout_weight
之类的内容。它基于我撰写的关于async data binding的旧文章。
,例如:
layout_width
您可以让ViewModel看起来像:
NotifyTask<T>
然后您的数据绑定将使用public async Task<Result> MyFunctionAsync();
来显示结果。数据绑定还可以使用public NotifyTask<Result> MyFunctionResults { get { ... } private set { ... /* raise PropertyChanged */ } }
public ICommand StartMyFunction { get; } = new DelegateCommand(() =>
{
MyFunctionResults = NotifyTask.Create(MyFunctionAsync());
});
和其他实用程序属性来显示/隐藏微调器和错误指示器。
请注意,此处不需要异步命令。该命令仅启动异步操作,异步数据绑定用于在数据到达时更新UI。