【WPF】Binding入門6。初期値はFallbackValueで指定する

C#

こんにちは、働くC#プログラマーのさんさめです。

WPFにおいてBindingは
「データとビューの疎結合化」
「コード記述の省力化」
などなど非常に重要な役割を果たします。
(後者は慣れないと恩恵を感じにくいですが)

その一方で、
何が起きているのか分かりにくいため、
一度Bindingでハマってしまうと
「まずどこから調べればいいのか分からない」
ということになりがちです。

そこで、私自身の知識の整理も兼ねて
Bindingについてまとめることにしました。

今回は第6回ということで、
FallbackValueについて説明します。

開発中にこんな困りごとはなかったでしょうか

  • Xamlデザイナー上でExpanderを開いておきたいが
    Bindingを使ってるので設定できない
  • DataContextがnullの時にBindingが成立せず
    意図しない見た目になる
  • VisibilityをBindingしているとxamlデザイナー上で
    常にVisibleになってしまい邪魔

これらに共通しているのは、
「Bindingを使ってるせいで普段の見た目を制御できない」
ことです。

xaml上で直接指定してよいのであれば、
IsExpanded=”True”
だとか、
Visibility=”Collapsed”
みたいに書いておけば済むのに…。
と歯がゆい経験をしたことはないでしょうか。

これを解決するのがFallbackValueです。

本記事ではFallbackValueの具体的な使い方、
活用方法について解説します。

Bindingを使いながら初期値を指定したり
引き出しとして覚えてもらうのが目標です

スポンサーリンク

FallbackValueとは

FallbackValue
Bindingを使うときに指定できるプロパティの1つです。

WPFコントロールの
Bindingできるプロパティには規定値が存在します。

たとえばWidthならNanですし、
VisibilityならVisibleです。

Bindingを指定しているにも関わらず
DataContextがnullであったり、
Binding評価の結果正常に値を返さなかったときに
この規定値が使われます。

FallbackValueを使うとその規定値をBindingごとに
任意の値に変更できます。

指定方法は次のような形になります

<Expander IsExpanded="{Binding IsExpanded, FallbackValue=True}">
    <TextBlock Text="あああ"/>
</Expander>

通常のパスだけ記述するBindingでは、

{Binding IsExpanded}

だけ書いて終わりですが、

, FallbackValue=True

の部分が追加されています。

これで、このBindingは
「IsExpandedプロパティにBindingしてね。
うまくいかないときはTrue扱いして」
という指定をしたことになります。

試しに使用する/しないで並べてみましょう。

このxamlではDataContextの指定を何もしていないので、
xamlスタイラー上ではBindingが成立しません。

ですが、FallbackValueを指定しているかどうかで、
Expanderの展開状況が異なっていることがわかります。

用例:詳細表示の欄を隠す

ここからは実例の紹介です。

先述したFallbackValueのサンプルでは
単にxamlスタイラー上で見やすくなる程度の差しかありません。

さて、ここで
左側に名前をリスト表示し、
いずれかの要素を選択状態にすると、
右側に詳細情報(年齢や性別、備考など)が
表示されるアプリケーションを考えます。

「備考」は情報が存在するときだけ出す
<Window x:Class="FallbackValueSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:FallbackValueSample"
        Title="MainWindow" Height="300" Width="300">
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BoolToVisible"/>
    </Window.Resources>
    <DockPanel>
        <ListBox x:Name="PersonList" ItemsSource="{Binding Person}" MinWidth="80"
                 Margin="4"
                 IsSynchronizedWithCurrentItem="True">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Border Margin="4" BorderThickness="1" BorderBrush="DarkGray" Padding="4">
            <StackPanel DataContext="{Binding SelectedItem, ElementName=PersonList}">
                <TextBlock Text="名前"/>
                <TextBox Text="{Binding Name}" IsReadOnly="True"/>
                <TextBlock Text="年齢"/>
                <TextBox Text="{Binding Age}" IsReadOnly="True"/>
                <TextBlock Text="性別"/>
                <TextBox Text="{Binding Gender}" IsReadOnly="True"/>
                <TextBlock Text="備考" Visibility="{Binding ExistsRemarks, Converter={StaticResource BoolToVisible}}"/>
                <TextBox Text="{Binding Remarks}" Visibility="{Binding ExistsRemarks, Converter={StaticResource BoolToVisible}}" IsReadOnly="True"/>
            </StackPanel>
        </Border>
    </DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FallbackValueSample
{
    public class MainWindowViewModel
    {
        public ICollection<People> Person { get; }
            = new List<People>();

        public MainWindowViewModel()
        {
            Person.Add(new People
            {
                Name = "太郎",
                Age = 20,
                Gender = People.GenderEnum.男,
            });
            Person.Add(new People
            {
                Name = "花子",
                Age = 18,
                Gender = People.GenderEnum.女,
                Remarks = "呼吸の使い手",
            });
            Person.Add(new People
            {
                Name = "次郎",
                Age = 16,
                Gender = People.GenderEnum.男,
                Remarks = "特級術師",
            });
        }
    }

    public class People
    {
        public enum GenderEnum
        {
            男,
            女
        }
        public string Name { get; set; } = string.Empty;
        public int Age { get; set; }
        public GenderEnum Gender { get; set; }
        public string Remarks { get; set; } = string.Empty;
        public bool ExistsRemarks => !string.IsNullOrEmpty(Remarks);
    }
}

シンプルなアプリケーションですが、
左側のリストで何も選択していないときにも、
備考欄が出てしまいます。

見えちゃってる…

これは、Visibilityを
ExistsRemarksプロパティで制御しているものの
SelectedItem自体がnullなため
Bindingに失敗しているからです。

<!-- ExistsRemakrsプロパティを持つオブジェクト自体が存在しない! -->
<TextBlock Text="備考" Visibility="{Binding ExistsRemarks, Converter={StaticResource BoolToVisible}}"/> 
<TextBox Text="{Binding Remarks}" Visibility="{Binding ExistsRemarks, Converter={StaticResource BoolToVisible}}" IsReadOnly="True"/>

そこで、FallbackValueの出番です。

上記のコードを以下のように、
Bindingの末尾に「, FallbackValue=Collapsed」を追加します

<TextBlock Text="備考" Visibility="{Binding ExistsRemarks, Converter={StaticResource BoolToVisible}, FallbackValue=Collapsed}"/> 
<TextBox Text="{Binding Remarks}" Visibility="{Binding ExistsRemarks, Converter={StaticResource BoolToVisible}, FallbackValue=Collapsed}" IsReadOnly="True"/>

すると、このように選択していないときにも
備考が表示されないようになりました。

選択していないときにも非表示にできた

FallbackValueとは、
このように用途こそ限定的ですが、
実用上も使える、
覚えていて損はないプロパティなのです。

TargetNullValueとの違い

似たBindingのプロパティに
TargetNullValueプロパティがあります。

こちらはBindingによって得られた値がnullの時に
表示される値を任意のものにできる仕組みです。

つまり、
「Binding自体は成功してるけども
それによって返された値がnull」
なので、
FallbackValueとはまた使用シチュエーションが異なります。

まとめ

まとめです。

  • Bindingと初期値指定を併用したい場合はFallbackValueを使う
  • Bindingが失敗しているときの見た目の担保にも使用できる
  • TargetNullValueとは用途が異なるので注意

最後までお読みいただき、ありがとうございました。

コメント

タイトルとURLをコピーしました