Antoine L
Antoine L

Reputation: 1

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

I'm starting out in using ReactiveUI.Validation. When using this.IsValid() as the parameter for CanExecute in a ReactiveCommand.Create(), the IsValid is one user input "late".

This is a ViewModel.cs to reproduce the problem, for testing I bind it to a WPF TextBox:

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;
    }
}

This is the Console output I get (notice when the command is executed):

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 

This is the console output I would expect:

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 

Am I using it correctly? Is there a way to force the validation before the InvokeCommand?

Thank you very much for your help!

Upvotes: 0

Views: 763

Answers (2)

Antoine L
Antoine L

Reputation: 1

Solved by the good folks at GitHub: https://github.com/reactiveui/ReactiveUI.Validation/issues/92

Copy of the post on 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.

It works with the new version 1.6.4 of ReactiveUI.Validation

Upvotes: 0

Progman
Progman

Reputation: 19545

There are several solutions to get out of that situation:

Solution 1: Don't use InvokeCommand

The obvious one is not to use InvokeCommand if you want to bypass the validation under any circumstances. Check the 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.

So you might want to call the SetMyProperty() method directly instead via the command.

Solution 2: Delay the subscription on MyProperty

You can use the Observable.Delay() extension to wait for a small amount of time before reacting for the property change. That way you can "give enough time" for the IsValid() check to switch before doing your InvokeCommand call. The code might look like this:

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

Upvotes: 1

Related Questions