< テキストボックスのフォーカス取得時にテキストを選択する | メモ: ValidationRule.ValidationStep プロパティによって ValidationRule に渡される値の型は異なる。 >

March 6, 2009

ItemsControl の要素を BindingGroup を使用して一括検証 & 更新する

validate.png

設定ダイアログなんかで、OK ボタンで一括チェックを行って反映、みたいなのを作る場合、個々の Binding に UpdateSourceTrigger="Explicit" を設定しておき、.NET Framework 3.5 から追加された BindingGroup で ValidationRule をまとめて走らせる実装をしてる。でも、ItemsControl の ItemTemplate で Binding を定義した場合、それらの Binding はそのままでは BindingGroup には入らないみたい。

BindingGroup が使えないなんて、項目ごとの BindingExpression を取得して更新するしかないの ? んなわけないよね ? と思って試行錯誤してみた結果、BindingGroup に Name プロパティを設定しておき、同時に更新したい Binding の BindingGroupName プロパティにその値を設定しておくと追加されることが分かった。
※ もちろん、同一のデータソースのものしか追加されないよ

一応、それなりの動作はするんだけど、なぜか最後の項目から順番に ValidationRule が走ってしまう。そんなもんだっけ ?

その他、気づいたこととか、メモとか。

  • BindingGroup と 個々の Binding の両方にうっかり Validation.Error ハンドラを設定してしまうと、両方走ってしまってえらい格好悪い (当たり前か)
  • ひょっとすると、一個の ValidationRule で引っかかった場合に以降の Validation を実行しない方法があるんじゃないか (調査中)
  • 個々の Binding に設定した ValidationRule の ValidationStep プロパティをうっかり UpdatedValue にしてしまうと、ValidationRule::Validate に渡される value は BindingExpression になる。(しかも更新後にわたってくるので意味がない)
  • 値の一意性をチェックするときなんかは、BindingGroup に ValidationStep を UpdatedValue に設定した ValidationRule を仕込んでおくとよい。
    (その場合は BindingGroup のトランザクション機能を利用する必要がある。IEditableObject を実装するんが面倒だけど ><)

というわけで、サンプル。

Window1.xaml

<Window x:Class="WpfSample.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfSample"
        Title="ItemsControl でデータ検証を実装する"
        SizeToContent="Height" Width="300">
    <Window.Resources>

        <local:People x:Key="PeopleSource"/>

        <ControlTemplate x:Key="ErrorTemplate">
            <StackPanel Orientation="Horizontal">
                <AdornedElementPlaceholder/>
                <TextBlock Foreground="Red" Text="×" Margin="3" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Bold" FontSize="{TemplateBinding FontSize}" />
            </StackPanel>
        </ControlTemplate>

        <DataTemplate x:Key="PeopleEditTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="NameColumn"/>
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <TextBlock Margin="5" Grid.Column="0" Text="{Binding Path=Name, StringFormat={}{0} の年齢:}" />
                <TextBox Margin="5,5,10,5" Grid.Column="1" Validation.ErrorTemplate="{StaticResource ErrorTemplate}">
                    <TextBox.Text>
                        <Binding Path="Age" Mode="TwoWay" UpdateSourceTrigger="Explicit" BindingGroupName="PeopleBindings">
                            <Binding.ValidationRules>
                                <local:PersonAgeValidationRule/>
                            </Binding.ValidationRules>
                        </Binding>
                    </TextBox.Text>
                </TextBox>
            </Grid>
        </DataTemplate>

    </Window.Resources>

    <StackPanel Margin="5">
        <ItemsControl 
            x:Name="ItemsPeople"
            ItemsSource="{Binding Source={StaticResource PeopleSource}}"
            ItemTemplate="{StaticResource PeopleEditTemplate}"
            Validation.Error="ItemsControl_ValidationError"
            Validation.ErrorTemplate="{x:Null}"
            Grid.IsSharedSizeScope="True">
            <ItemsControl.BindingGroup>
                <BindingGroup Name="PeopleBindings" NotifyOnValidationError="True"/>
            </ItemsControl.BindingGroup>
        </ItemsControl>
        <Button Margin="5" Content="UpdateSource" Click="UpdateSourceButton_Click"/>
    </StackPanel>
</Window>

コードビハインド

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfSample
{
    /// <summary>
    /// Window1.xaml の相互作用ロジック
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1 ()
        {
            InitializeComponent ();
        }

        private void UpdateSourceButton_Click (object sender, RoutedEventArgs e)
        {
            if (ItemsPeople.BindingGroup.UpdateSources ())
                MessageBox.Show ("更新しました", "更新完了", MessageBoxButton.OK, MessageBoxImage.Information);
        }

        private void ItemsControl_ValidationError (object sender, ValidationErrorEventArgs e)
        {
            if (e.Action == ValidationErrorEventAction.Added)
                MessageBox.Show (e.Error.ErrorContent.ToString (), "検証エラー", MessageBoxButton.OK, MessageBoxImage.Error);
        }

    }
}

バインドするデータとか。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Controls;

namespace WpfSample
{

    public class Person : INotifyPropertyChanged
    {
        private string name;
        private int age;

        public Person (string name, int age)
        {
            this.name = name;
            this.age = age;
        }

        public string Name
        {
            get { return name; }
        }

        public int Age
        {
            get { return age; }
            set
            {
                age = value;
                NotifyPropertyChanged ("Age");
            }
        }

        #region INotifyPropertyChanged の実装

        public event PropertyChangedEventHandler  PropertyChanged;

        private void NotifyPropertyChanged (string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler (this, new PropertyChangedEventArgs (propertyName));
        }

        #endregion
    }

    public class People : ObservableCollection<Person>
    {
        public People ()
        {
            Add (new Person ("ba", 18));
            Add (new Person ("bi", 19));
            Add (new Person ("bu", 20));
            Add (new Person ("be", 21));
            Add (new Person ("bo", 22));
        }
    }

    public class PersonAgeValidationRule : ValidationRule
    {
        public override ValidationResult Validate (object value, System.Globalization.CultureInfo cultureInfo)
        {
            int age = 0;
            var str = value as string;

            if (str == null)
            {
                try { age = (int) value; }
                catch { return new ValidationResult (false, "年齢を入力してください。"); }
            }
            else
            {
                if (!int.TryParse (str, out age))
                    return new ValidationResult (false, "年齢は数値で入力してください。");
            }

            if (0 > age || 1000 < age)
                return new ValidationResult (false, "年齢は 0 歳以上 1000 歳以下で入力してください。");

return ValidationResult.ValidResult;
}
}

}


トラックバック

このエントリーにトラックバック:
http://frog.raindrop.jp/cgi-bin/mt/mt-tb.cgi/2338

コメント

コメントする

※ コメントスパム対策のため、コメント本文はおはよう、こんにちわ、こんばんわのいずれかより始めるようにしてください。

name:
email:

※ 必要ですが、表示しません。

url:
情報を保存する ?