読者です 読者をやめる 読者になる 読者になる

Nullableへのデータバインド

例えば以下のようなクラスがあったとする。

public class User : INotifyPropertyChanged {

  private string name;
  public string Name {
    get { return this.name; }
    set {
      if (this.name != value) {
        this.name = value;
        OnPropertyChanged("Name");
      }
    }
  }

  private int age;
  public int Age {
    get { return this.age; }
    set {
      if (this.age != value) {
        this.age = value;
        OnPropertyChanged("Age");
      }
    }
  }
  
  public event PropertyChangedEventHandler PropertyChanged;

  protected void OnPropertyChanged(string propertyName) {
    if (PropertyChanged != null) {
      PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}

それを画面にバインドする。以下のような感じ。

<UserControl x:Class="NullableApp.MainPage"
  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" 
  mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
  xmlns:local="clr-namespace:NullableApp">
  <UserControl.DataContext>
    <local:User />
  </UserControl.DataContext>
  <Grid x:Name="LayoutRoot" HorizontalAlignment="Left" VerticalAlignment="Top"
    Margin="10,10,0,0">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="50" />
      <ColumnDefinition Width="100" />
    </Grid.ColumnDefinitions>
    
    <TextBlock Text="Name" />
    <TextBox Text="{Binding Name,Mode=TwoWay,ValidatesOnExceptions=true,NotifyOnValidationError=true}" 
      Grid.Column="1" />
    
    <TextBlock Text="Age" Grid.Row="1" />
    <TextBox Text="{Binding Age,Mode=TwoWay,ValidatesOnExceptions=true,NotifyOnValidationError=true}" 
      Grid.Row="1" Grid.Column="1" />
    
  </Grid>
</UserControl>

画面表示すると以下のような感じ。値は適当に設定。

テキストボックスを両方とも空にしてフォーカスを外すと以下のような感じ。

Age のテキストボックスにエラーが出てます。まぁ、 int 型に空文字が設定されてしまうんで当然ですね。


で、例えばアプリケーションの要件で Age テキストボックスに値を設定したくないってのが出てきたとします。とりあえず、 User.Age を int から Nullable にしてみます。

private int? age;
public int? Age {
  get { return this.age; }
  set {
    if (this.age != value) {
      this.age = value;
      OnPropertyChanged("Age");
    }
  }
}

実行してみます。以下のような感じ。

やっぱりエラーが出ます。気分的には null が設定されてほしいのですがだめです。

テキストボックスを空に変更した場合、書き戻される値は空文字になります。なので例え書き戻し先の型が Nullable でも T が空文字を許容しない型(今回の場合は int )だったら、設定できないんですね。

大抵の場合は、空文字の場合は null が設定されてくれれば問題ないので、 空文字から null に変換できれば OK なわけです。ってことでここで ValueConverter の出番です。

以下のような、ValueConverter を作ります。

public class NullableConverter : IValueConverter {
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
    return value;
  }

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
    if (targetType.GetGenericTypeDefinition() != typeof(Nullable<>)) {
      return value;
    }
    if (!(value is string)) {
      return value;
    }
    if (!string.IsNullOrEmpty((string)value)) {
      return value;
    }

    return null;
  }
}

作った NullableConverter を設定してみます。

<UserControl.Resources>
  <local:NullableConverter x:Key="NullableConverter" />
</UserControl.Resources>
<TextBlock Text="Age" Grid.Row="1" />
<TextBox Text="{Binding Age,Mode=TwoWay,ValidatesOnExceptions=true,NotifyOnValidationError=true,Converter={StaticResource NullableConverter}}" 
  Grid.Row="1" Grid.Column="1" />


実行してみます。

画像じゃ動きは全く分かりませんが、うまくいってちゃんと null が設定されてました。


※さて、これがたぶん Silverlight 3 の最後の記事になるのかなぁー