ItemsControl の要素を BindingGroup を使用して一括検証 & 更新する
設定ダイアログなんかで、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
コメント