かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WPFでTextBoxに入力エラーがないときだけ押せるボタンを実現したい

1つ前でVMでやるのがおすすめですよって書いたけどどうやるの?っていう記事です。

blog.okazuki.jp

今回はReactivePropertyの組み込みのエラーチェック機能を使ってやってみます。まず。intしか受け付けないValidationAttributeを作ります。

using System.ComponentModel.DataAnnotations;

namespace WpfApplication12
{
    public class IntAttribute : ValidationAttribute
    {
        public IntAttribute(string errorMessage) : base(errorMessage)
        {
        }

        public override bool IsValid(object value)
        {
            int temp;
            return int.TryParse(value?.ToString(), out temp);
        }
    }
}

そして、それをセットしたReactivePropertyを定義します。

public class MainWindowViewModel : BindableBase
{
    [Int("整数で入力してね")]
    public ReactiveProperty<string> Input1 { get; }
    [Int("整数で入力してね")]
    public ReactiveProperty<string> Input2 { get; }

    public ReactiveCommand ExecuteCommand { get; }

    public MainWindowViewModel()
    {
        this.Input1 = new ReactiveProperty<string>("0")
            .SetValidateAttribute(() => this.Input1);
        this.Input2 = new ReactiveProperty<string>("0")
            .SetValidateAttribute(() => this.Input2);

        this.ExecuteCommand = new[]
            {
                this.Input1.ObserveHasErrors,
                this.Input2.ObserveHasErrors,
            }.CombineLatestValuesAreAllFalse()
            .ToReactiveCommand();
    }

}

ExecuteCommandは、Input1とInput2でエラーが全部Falseだったら実行可能なコマンドに加工しています。CombineLatestValuesAreAllFalse(True版もあるよ)はReactivePropertyのちょっとした便利メソッドですね。

これを以下のような感じでXAMLにバインドすれば出来上がりです。

<Window x:Class="WpfApplication12.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication12"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="350"
        Width="525">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <StackPanel>
        <TextBox x:Name="TextBoxInput1"
                 Text="{Binding Input1.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Text="{Binding (Validation.HasError), ElementName=TextBoxInput1}" />
        <TextBox x:Name="TextBoxInput2"
                 Text="{Binding Input2.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Text="{Binding (Validation.HasError), ElementName=TextBoxInput2}" />
        <Button Content="OK" 
                Command="{Binding ExecuteCommand}"/>
    </StackPanel>
</Window>

めでたしめでたし。