ReactiveCommand返回值并查看反馈循环

时间:2015-10-03 23:21:19

标签: c# wpf validation mvvm reactiveui

我熟悉MVVM的概念并使用过MvvmCross,但我正在尝试使用ReactiveUI并尝试围绕一些概念。

我正在为WPF编写一个工具(可能分支到其他框架),供设计人员创建和编辑数据文件,然后由另一个最终用户程序使用。我有一个代表ViewModel文档的DataModel,并希望对数据进行验证,以告知设计人员任何可能发生的破坏行为。底层类看起来像这样:

public class DataModel
{
    // member data here

    public void Validate(Validator validator)
    {
        // perform specific complex validation here and add errors to validator
    }
}

// aggregator for validation errors
public class Validator
{
    public IList<Error> Errors { get; }
}

ViewModel应该有一个ReactiveCommand Validate View可以绑定到一个按钮,但一旦完成,我想向用户显示一个显示验证错误的对话框,或者没有找到。有没有直接的方式将Validator.Errors传递回View,还是我必须为IObservable订阅ReactiveListView属性才能订阅?

开始编辑

感谢评论中的帮助,我可以弄清楚如何使用UserErrors节省用户确认。仍试图找出验证的返回值。这是我到目前为止ViewModel

中的内容
public class ViewModel
{
    public DataModel Model { get; }
    public ReactiveCommand<List<Error>> { get; protected set; }

    public ViewModel(DataModel model)
    {
        Model = model;

        Validate = ReactiveCommand.CreateAsyncObservable<List<Error>>(_ => 
        {
            Validator validator = new Validator();
            Model.Validate(validator);

            // not sure how to turn validator.Errors into the IObservable<List<Error>> that ReactiveUI wants me to return.
        });
    }
}

Validate成为ReactiveCommand<Error>并致电validator.Errors.ToObservable()会更好吗?我还可以在我的视图中迭代错误吗?

结束修改

同样,我想要一个首先执行验证的保存功能。如果未找到验证错误,则会将DataModel保存到文件。如果发现错误,View应通知用户并在保存之前获得确认。什么是ReactiveUI处理此反馈循环的方式:

执行Save命令 - &gt;验证(可能调用Validate命令?) - &gt;如果错误,则请求View确认 - >保存确认或什么都不做

2 个答案:

答案 0 :(得分:0)

正如评论中所提到的,这里有一些例子: https://github.com/reactiveui/ReactiveUI/blob/master/docs/basics/errors.md

和: https://github.com/reactiveui/ReactiveUI.Samples/tree/master/ReactiveUI.Samples.Routing

关于不同错误类型的不同对话框,一种方法是你可以将它从RecoveryCommand中删除。例如,根据您提供的内容显示不同的选项,当您触发UserError时,您可以提供RecoveryCommands,然后根据它执行自定义逻辑。

然后在处理错误的视图模型中,您可以执行以下操作:

        // The show command will return the decision from the user how to proceed with a error.
        // The UserError will have a number of recovery options associated with it, which the dialog 
        // will present to the user. In testing mode this will likely be the test triggering the recovery command.
        // We will wait until one of those recovery commands is executed, then get the result from it.
        ShowCommand = ReactiveCommand.CreateAsyncObservable(x =>
        {
            var userError = x as UserError;

            // We must always have a valid user error. 
            if (userError == null)
            {
                return Observable.Throw<RecoveryOptionResult>(new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Expected a UserError but got {0}", x)));
            }

            Error = userError;
            Message = Error.ErrorMessage;

            // This fancy statement says go through all the recovery options we are presenting to the user
            // subscribe to their is executing event, if the event fires, get the return result and pass that back
            // as our return value for this command.
            return (Error.RecoveryOptions.Select(cmd => cmd.IsExecuting.SelectMany(_ => !cmd.RecoveryResult.HasValue ? Observable.Empty<RecoveryOptionResult>() : Observable.Return(cmd.RecoveryResult.Value)))).Merge().Take(1);
        });

我假设你说只会执行一个命令。我基本上将不同的恢复命令IsExecuting合并为一个,当点击第一个时,我认为这是我想要的恢复选项。

然后,在您抛出恢复命令的位置,您可以根据需要进行处理:

        var retryCommand = new RecoveryCommand("Retry") { IsDefault = true };
        retryCommand.Subscribe(_ => retryCommand.RecoveryResult = RecoveryOptionResult.RetryOperation);

        var userError = new UserError(errorMessage, errorResolution, new[] { retryCommand, RecoveryCommand.Cancel });

        switch (await UserError.Throw(userError))
        {
            case RecoveryOptionResult.RetryOperation:
                await Setup();
                break;
            case RecoveryOptionResult.FailOperation:
            case RecoveryOptionResult.CancelOperation:
                if (HostScreen.Router.NavigateBack.CanExecute(null))
                {
                    HostScreen.Router.NavigateBack.Execute(null);
                };

                break;
            default:
                throw new ArgumentOutOfRangeException();
        }

另一种方法可能是派生出UserError类,并根据出现的类类型显示不同的对话框。例如,根据类类型保​​留要显示的控件字典。当您注册处理程序以显示错误对话框时,只需显示相应的对话框。

答案 1 :(得分:-2)

下面的示例显示了使用命令进行验证。我使用了一个名为NavigationCommands.Search的内置do-nothing命令。按钮:单击调用DataModel:验证如果发现错误,则填充传入的Validator。

MainWindow.xaml

<Window ...>    
    <Window.CommandBindings>
        <CommandBinding Command="NavigationCommands.Search" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed"/>
    </Window.CommandBindings>

    <Grid>
        ...
        <Button Content="Button" Command="NavigationCommands.Search" HorizontalAlignment="Left" Margin="229,28,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75"/>
    </Grid>
</Window>

Mainwindow.xaml.cs

namespace WpfCommands
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        DataModel dm = new DataModel();

        public MainWindow()
        {
            InitializeComponent();
        }

        private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Validator myValidator = new Validator();

            dm.Validate(myValidator);
            if (myValidator.Errors.Count > 0)
            {
                MessageBox.Show("Errors !");
                // do something with errors
            }
        }
    }
}

DataModel.cs

namespace WpfCommands
{
    public class DataModel
    {
        public void Validate(Validator v)
        {
            // do some validation
            v.Errors.Add(new Error() { Message = "Error1" });
        }
    }

    // aggregator for validation errors
    public class Validator
    {
        IList<Error> _errors = new List<Error>();

        public IList<Error> Errors { get { return _errors; } }
    }

    public class Error
    {
        public string Message { get; set; }
    }
}