こんにちは、働く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とは用途が異なるので注意
最後までお読みいただき、ありがとうございました。
シリーズ記事
Binding入門はシリーズ記事となっております。
全ての記事に以下からアクセスできます
コメント