我熟悉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
订阅ReactiveList
或View
属性才能订阅?
开始编辑
感谢评论中的帮助,我可以弄清楚如何使用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
确认 - >保存确认或什么都不做
答案 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; }
}
}