ReactiveUI.Validation: this.IsValid() 在调用 ReactiveCommand 后更新

ReactiveUI.Validation: this.IsValid() updated after ReactiveCommand is invoked

我开始使用 ReactiveUI.Validation。当在 ReactiveCommand.Create() 中使用 this.IsValid() 作为 CanExecute 的参数时,IsValid 是一个用户输入“迟到”。

这是一个 ViewModel.cs 重现问题的方法,为了测试,我将它绑定到 WPF 文本框:

public class ViewModel : ReactiveValidationObject<ViewModel>
{
    private readonly ReactiveCommand<string, Unit> Command;

    private string _myProperty;
    public string MyProperty
    {
        get => _myProperty;
        set => this.RaiseAndSetIfChanged(ref _myProperty, value);
    }

    public ViewModel()
    {
        Command = ReactiveCommand.Create<string>(x => SetMyProperty(x), this.IsValid());

        this
            .WhenAnyValue(x => x.MyProperty)
            .Do(x => Console.WriteLine("Property value = " + x))
            .InvokeCommand(Command);

        this.ValidationRule(
            viewModel => viewModel.MyProperty,
            x => !string.IsNullOrEmpty(x) && x.Length > 3,
            "Enter a value");

        this.IsValid()
            .Subscribe(x => Console.WriteLine("IsValid = " + x));
    }

    private Unit SetMyProperty(string value)
    {
        Console.WriteLine("Entered method");
        return Unit.Default;
    }
}

这是我得到的控制台输出(执行命令时注意):

Property value = 1  
Property value = 12  
Property value = 123  
Property value = 1234  
IsValid = True  
Property value = 12345  
Entered method  
Property value = 1234  
Entered method  
Property value = 123  
Entered method  
IsValid = False  
Property value = 12  
Property value = 1 

这是我期望的控制台输出:

Property value = 1  
Property value = 12  
Property value = 123  
Property value = 1234  
IsValid = True  
Entered method  
Property value = 12345  
Entered method  
Property value = 1234  
Entered method  
Property value = 123  
IsValid = False  
Property value = 12  
Property value = 1 

我使用正确吗?有没有办法在 InvokeCommand 之前强制进行验证?

非常感谢您的帮助!

有几种解决方案可以摆脱这种情况:

解决方案 1:不要使用 InvokeCommand

如果您想在任何情况下绕过验证,显而易见的是不要使用 InvokeCommand。检查 documentation of InvokeCommand():

Hint InvokeCommand respects the command's executability. That is, if the command's CanExecute method returns false, InvokeCommand will not execute the command when the source observable ticks.

因此您可能希望通过命令直接调用 SetMyProperty() 方法。

解决方案 2:延迟 MyProperty

的订阅

您可以使用 Observable.Delay() 扩展程序等待一小段时间,然后再对 属性 更改做出反应。这样你就可以“给足够的时间”让 IsValid() 检查在进行你的 InvokeCommand 调用之前进行切换。代码可能如下所示:

this.WhenAnyValue(x => x.MyProperty)
    .Delay(TimeSpan.FromMilliseconds(100))
    .Do(x => Console.WriteLine("Property value = " + x))
    .InvokeCommand(Command);

由 GitHub 的好心人解决:https://github.com/reactiveui/ReactiveUI.Validation/issues/92

post 在 GitHub 上的副本:

If you place a call to this.WhenAnyValue below a call to this.ValidationRule, you'll get the following behavior:

IsValid = False
Property value = 1
Property value = 12
Property value = 123
IsValid = True
Property value = 1234
Entered method
Property value = 12345
Entered method
Property value = 1234
Entered method
IsValid = False
Property value = 123
Property value = 12
Property value = 1

The modified code looks as such:

    public class ViewModel : ReactiveValidationObject<ViewModel>
    {
        private readonly ReactiveCommand<string, Unit> Command;
    
        private string _myProperty;
        public string MyProperty
        {
            get => _myProperty;
            set => this.RaiseAndSetIfChanged(ref _myProperty, value);
        }
    
        public ViewModel()
        {
            Command = ReactiveCommand.Create<string>(x => SetMyProperty(x), this.IsValid());
    
            this.ValidationRule(
                viewModel => viewModel.MyProperty,
                x => !string.IsNullOrEmpty(x) && x.Length > 3,
                "Enter a value");
            
            // The call to WhenAny is now placed below a call to ValidationRule.
            this.WhenAnyValue(x => x.MyProperty)
                .Do(x => Console.WriteLine("Property value = " + x))
                .InvokeCommand(Command);
    
            this.IsValid()
                .Subscribe(x => Console.WriteLine("IsValid = " + x));
        }
    
        private Unit SetMyProperty(string value)
        {
            Console.WriteLine("Entered method");
            return Unit.Default;
        }
    }

Both WhenAny and ValidationRule are using CurrentThreadScheduler since #97, so the call order matters now. Hopefully we will deploy the new version of the package to NuGet soon.

它适用于 ReactiveUI.Validation

的新版本 1.6.4