Reputation: 1
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
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 = 1The 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
Reputation: 19545
There are several solutions to get out of that situation:
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'sCanExecute
method returnsfalse
,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.
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