オルタナティブ・ブログ > IT's my business >

IT業界のコメントマニアが始めるブログ。いつまで続くのか?

(XAML#15)「バインディングと値コンバーター」

»

WPF でバインディングを使う際、「数値」(intやdouble)と「文字列」(string)といった単純なものであれば、自動的に型変換して連動させてくれます。しかし、単純に変換できない型でも連動させたいことがあります。このようなときに便利なのが、「値コンバーター(value converter)」と呼ばれる機能です。

たとえば、CheckBox コントロールの IsChecked プロパティと、TextBlock コントロールの Visibility プロパティを連動させることを考えてみます。ここで、次のように記述しても動作しません。IsChecked プロパティは bool? 型、Visibility プロパティは Visibility型であり、型がまったく異なるためです。

[XAML]
<StackPanel>
    <CheckBox x:Name="check1" Content="Visibility" />
    <TextBlock Text="Sample Text" Visibility="{Binding ElementName=check1, Path=IsChecked}" />
</StackPanel>

そこで、bool? 型と Visibility 型を変換するための値コンバーターを作ります。値コンバーターは IValueConverter インターフェイスを実装するクラスであり、この場合は次のように記述できます。

[コード]
public class BoolVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return ((bool?)value == true) ? Visibility.Visible : Visibility.Hidden;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

この例では、バインディングを定義したコントロールは参照先の CheckBox の値を一方的に受け取るだけなので ConvertBack メソッドは使いません(IValueConverter で定義されているため、なくすことはできません)。

クラスは、そのままでは実体がなく使えないため、通常はリソース中にこれを定義します。以下は、そのプログラム例です。

<Window x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApp1"
    Title="MainWindow" Height="325" Width="525">
    <Window.Resources>
        <local:BoolVisibilityConverter x:Key="BoolVisibilityConverter" />
    </Window.Resources>
    <StackPanel>
        <CheckBox x:Name="check1" Content="Visibility" />
        <TextBlock Text="Sample Text" Visibility="{Binding ElementName=check1, Path=IsChecked, Converter={StaticResource BoolVisibilityConverter}}" />
    </StackPanel>
</Window>


冒頭で、自分自身のアプリケーションを指すlocalという名前空間を定義していることに注意してください。<Window.Resources>では、この名前空間で定義された BoolVisibilityConverter クラスの実態を定義し、クラス名と同じキー名で参照できるようにしています。TextBlock の Visibility プロパティは Binding の指定に Converter が追加されています。

前述のとおり、この例では参照先から一方的に値を受け取るだけなので、バインディングの定義に、明示的に「Mode=OneWay」を追加しておいてもよいでしょう。逆に、参照先と参照元の双方のコントロールで値を変化させる場合は、「Mode=TwoWay」とします。一般的なコントロールのバインディングは TwoWay になっていますが、逆方向のバインディングが行われる可能性がある場合は、前述の値コンバーター(BoolVisibilityConverter)中の ConvertBack メソッドで、逆方向の値変換を実装しておく必要があります。

また、値を参照先に渡す、というバインディングもあります。前述の例を、次のように置き換えてみます。

[XAML]
<Window.Resources>
    <local:VisibilityBoolConverter x:Key="VisibilityBoolConverter" />
</Window.Resources>
<StackPanel>
    <CheckBox Content="Visibility" IsChecked="{Binding ElementName=tblock1, Path=Visibility, Mode=OneWayToSource, Converter={StaticResource VisibilityBoolConverter}}" />
    <TextBlock x:Name="tblock1" Text="Sample Text" />
</StackPanel>

[コード]
public class VisibilityBoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return (Visibility)value == Visibility.Visible;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return ((bool?)value == true) ? Visibility.Visible : Visibility.Hidden;
    }
}

ここでは、CheckBox の IsChecked プロパティにバインディングを設定し、tblock1コントロール(TextBlock型)の Visibility プロパティを変化させています。バインディングのモードは「Mode=OneWayToSource」にしています。前述のとおり、多くのプロパティではバインディングは双方向(TwoWay)であり、自動的に逆方向のバインディングも行われるため、このような指定は必要ありません。

ここで、「Mode=OneWayToSource」を外す(または「Mode=TwoWay」にする)と、少しだけ動作が変わります。双方向の場合は、TextBlock 側の Visibility から CheckBox へも値が伝達するため、TextBlock の Visibility が初期状態で Visible であることが(値コンバーターを経由して)CheckBox 側の IsChecked に設定されるため、チェックされた状態になるのです。このときのために、値コンバーター(VisibilityBoolConverter)の Convert メソッドを定義しています。

なお、値コンバーターの実体は、必ずしもリソースで定義する必要はありません。プロパティ要素構文を使えば、以下のように XAML 中でコンバーターのオブジェクトを記述して割り当てることもできます。

[XAML]
<StackPanel>
    <CheckBox x:Name="check1" Content="Visibility #1" />
    <TextBlock Text="Sample Text">
        <TextBlock.Visibility>
            <Binding ElementName="check1" Path="IsChecked">
                <Binding.Converter>
                    <local:BoolVisibilityConverter />
                </Binding.Converter>
            </Binding>
        </TextBlock.Visibility>
    </TextBlock>
</StackPanel>

細かいことを言えば、このように記述するとコンバーターを割り当てるたびに毎回オブジェクトを生成することになり、用途が同じオブジェクトを無駄に生成することになります。つまり、ウィンドウやアプリケーションリソースとして定義しておく方が効率はよくなります。アプリケーション内で共通利用するのであれば、BoolVisibilityConverter クラス中に、コンバーターオブジェクトを静的メンバーとして定義しておくこともできます。

[コード]
public class BoolVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return ((bool?)value == true) ? Visibility.Visible : Visibility.Hidden;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    public static BoolVisibilityConverter Converter = new BoolVisibilityConverter(); // 値コンバーターの実体
}

XAML では x:Static を使って、この静的メンバーを参照します(local名前空間の定義は必要です)。

[XAML]
<StackPanel>
    <CheckBox x:Name="check1" Content="Visibility #1" />
    <TextBlock Text="Sample Text" Visibility="{Binding ElementName=check1, Path=IsChecked, Converter={x:Static local:BoolVisibilityConverter.Converter}}" />
</StackPanel>

Xaml15

Comment(0)