Display validation errors using ReactiveProperty

3 minute read

I felt that I searched for the same content several times, so I decided to leave it as a memorandum.
When adding validation error handling, we often implement the active state of the button as well, so leave it as well.

Specifications / created

  • ReactiveProperty
    • 7.2.0
      –Validation attributes used
      –Required item confirmation (Required)
      –Integer / real number confirmation (self-made Int / Double Validation)
      –Check the range (Set the range appropriately with Range (here, set from -100 to 100))
      –If there are no errors at all three input points, the button is activated.

ValidationDemo

appearance

The left is the initial state. The button is inactive.
If you enter a character string that causes an error in the middle.
The right is the state without error. The button is active.
window.png

Source code

This time I implemented it in WPF.
ʻIntValidationAttribute, DoubleValidationAttribute is a self-made validation attribute class. I created the class name of my own validation attribute according to the rule HogeValidationAttribute, but it seems that ʻAttribute can be omitted when actually using it as an attribute.

MainWindowViewModel.cs


namespace ValidationDemo.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private string _title = "Prism Application";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        [Required(ErrorMessage = "Required item")]
        public ReactiveProperty<string> InputText1 { get; }

        [Required(ErrorMessage = "Required item")]
        [IntValidation(ErrorMessage = "Please enter an integer")]
        [Range(-100, 100, ErrorMessage = "Please enter a value within the range")]
        public ReactiveProperty<string> InputText2 { get; }
        
        [Required(ErrorMessage = "Required item")]
        [DoubleValidation(ErrorMessage = "Please enter a real number")]
        [Range(-100.0, 100.0, ErrorMessage = "Please enter a value within the range")]
        public ReactiveProperty<string> InputText3 { get; }

        public ReadOnlyReactiveProperty<string> ErrorText1 { get; }
        public ReadOnlyReactiveProperty<string> ErrorText2 { get; }
        public ReadOnlyReactiveProperty<string> ErrorText3 { get; }

        public ReactiveCommand ExecCommand { get; }

        public MainWindowViewModel()
        {
            //Validation attribute setting
            this.InputText1 = new ReactiveProperty<string>().SetValidateAttribute(() => this.InputText1);
            this.InputText2 = new ReactiveProperty<string>().SetValidateAttribute(() => this.InputText2);
            this.InputText3 = new ReactiveProperty<string>().SetValidateAttribute(() => this.InputText3);

            //To hide the validation error when the window is initially displayed, set the mode as shown below.
            //this.InputText1 = new ReactiveProperty<string>(mode: ReactivePropertyMode.Default | ReactivePropertyMode.IgnoreInitialValidationError).SetValidateAttribute(() => this.InputText1);

            //Validation error display settings
            this.ErrorText1 = this.InputText1.ObserveErrorChanged
                .Select(x => x?.Cast<string>().FirstOrDefault())
                .ToReadOnlyReactiveProperty();
            this.ErrorText2 = this.InputText2.ObserveErrorChanged
                .Select(x => x?.Cast<string>().FirstOrDefault())
                .ToReadOnlyReactiveProperty();
            this.ErrorText3 = this.InputText3.ObserveErrorChanged
                .Select(x => x?.Cast<string>().FirstOrDefault())
                .ToReadOnlyReactiveProperty();

            //Active setting of execution command
            this.ExecCommand =
                new[]
                {
                    this.InputText1.ObserveHasErrors,
                    this.InputText2.ObserveHasErrors,
                    this.InputText3.ObserveHasErrors,
                }
                .CombineLatestValuesAreAllFalse()   //Active setting if all are error-free
                .ToReactiveCommand();
        }
    }
}

It’s a little off topic, but here we use Label to display error text.
If you are interested, please replace it with TextBlock as appropriate.

In general, if you need limited text support, such as using short sentences in the user interface (UI), you should use the TextBlock element. If you need minimal text support, use Label.
Performance Optimization: Text

MainWindow.xaml


<Window x:Class="ValidationDemo.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}" Height="350" Width="525" >

    <Window.Resources>
        <Style x:Key="ValidationStyle" TargetType="Label">
            <Setter Property="Foreground" Value="Red" />
        </Style>
    </Window.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Label Grid.Row="0" Content="Text 1(String)" HorizontalAlignment="Left" />
        <Label Grid.Row="0" Content="{Binding ErrorText1.Value}" Style="{StaticResource ValidationStyle}" HorizontalAlignment="Right" />
        <TextBox Grid.Row="1" Text="{Binding InputText1.Value, UpdateSourceTrigger=PropertyChanged}" />

        <Label Grid.Row="2" Content="Text 2(integer)" HorizontalAlignment="Left" />
        <Label Grid.Row="2" Content="{Binding ErrorText2.Value}" Style="{StaticResource ValidationStyle}" HorizontalAlignment="Right" />
        <TextBox Grid.Row="3" Text="{Binding InputText2.Value, UpdateSourceTrigger=PropertyChanged}" />

        <Label Grid.Row="4" Content="Text 3(Real number)" HorizontalAlignment="Left" />
        <Label Grid.Row="4" Content="{Binding ErrorText3.Value}" Style="{StaticResource ValidationStyle}" HorizontalAlignment="Right" />
        <TextBox Grid.Row="5" Text="{Binding InputText3.Value, UpdateSourceTrigger=PropertyChanged}" />

        <Button Grid.Row="6" Content="button" Command="{Binding ExecCommand}" />
    </Grid>
</Window>

Validation.cs


namespace ValidationDemo.Models
{
    public class IntValidationAttribute : ValidationAttribute
    {
        public override bool IsValid(object value)
            => int.TryParse(value.ToString(), out var _);
    }

    public class DoubleValidationAttribute : ValidationAttribute
    {
        public override bool IsValid(object value)
            => double.TryParse(value.ToString(), out var _);
    }
}

Reference link

Comfortable MVVM with Reactive Programming ReactiveProperty Overview 2020 Part 1
[C #] ReactiveProperty I don’t know at all! FAQ collection for people [corrected]
Sample validation using ReactiveProperty and DataAnnotations in Xamarin.Forms (try to create your own validation rule)