具有返回值

时间:2016-12-07 10:11:12

标签: c# wpf mvvm lambda

我刚刚开始用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的结果。 所以我现在通过重写函数来解决这个问题。它不是填充本地字典变量,而是填充全局字典变量。 这是我能够找到访问函数数据的唯一方法。 我只是想知道是否有另一种方法可以做到这一点,或者这是唯一/最佳方式。

希望这更好。

2 个答案:

答案 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,从服务器请求数据并在数据到达后更新MyPropertyMyProperty触发对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。