かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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>

めでたしめでたし。