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

かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

アニバーサリーアップデートでのコンパイル時データバインディングの強化点 Functions in binding paths

UWP XAML

なんて訳すんでしょうね。バインディングパス内関数とか?

アニバーサリーアップデートと共に降ってきたBuild 14393のSDKですが、こいつをMin versionに設定したプロジェクトでのみコンパイル時データバインディングの新機能が有効になります。つまりアニバーサリーアップデート未満では使えない…!

さて、どんなものか見て行ってみましょう。

簡単に言うと、コンパイル時データバインディングのパスの中にキャストや関数呼び出しが書けるようになってます。キャストは今回説明するFunctions in binding pathsの機能ではないですがバインディングのパスが強化されたという点では同じです。

どんなもんか見て行ってみましょう。 例として以下のようなViewModel(PrismのBindableBase使用)があるとします。

public class MainPageViewModel : BindableBase
{
    private string input;

    public string Input
    {
        get { return this.input; }
        set { this.SetProperty(ref this.input, value); }
    }

    public static string ToUpper(string x)
    {
        return x?.ToUpper();
    }
}

これをMainPageのDataContextにセットします。

<Page.DataContext>
    <local:MainPageViewModel />
</Page.DataContext>

そして、以下のようにキャストしてパスを書くことができます!

<TextBox Text="{x:Bind ((local:MainPageViewModel)DataContext).Input, Mode=TwoWay}" />

今まではコードビハインドとかでキャストして公開するプロパティ定義するとかしないといけなかったですが、それをしなくても書ける…!がめんどくさい!!ので今まで通りコードビハインドにこんなプロパティ生やすほうがいいと思うの!

public sealed partial class MainPage : Page
{
    private MainPageViewModel ViewModel => this.DataContext as MainPageViewModel;

    public MainPage()
    {
        this.InitializeComponent();
    }
}

さて本題です。関数が呼べます。

<Page x:Class="App64.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App64"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Page.DataContext>
        <local:MainPageViewModel />
    </Page.DataContext>
    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBox Text="{x:Bind ViewModel.Input, Mode=TwoWay}" />
        <TextBlock Text="{x:Bind local:MainPageViewModel.ToUpper(ViewModel.Input), Mode=OneWay}" />
    </StackPanel>
</Page>

先ほどViewModelに定義してたToUpperメソッドを呼び出しています。インスタンスメソッドでも呼べます。ToUpperメソッドを例えばコードビハインドのインスタンスメソッドとして以下のように定義したとします。

public sealed partial class MainPage : Page
{
    private MainPageViewModel ViewModel => this.DataContext as MainPageViewModel;

    public MainPage()
    {
        this.InitializeComponent();
    }
    public string ToUpper(string x)
    {
        return x?.ToUpper();
    }
}

そうすると、こんな風に書けます。

<Page x:Class="App64.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App64"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Page.DataContext>
        <local:MainPageViewModel />
    </Page.DataContext>
    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBox Text="{x:Bind ViewModel.Input, Mode=TwoWay}" />
        <TextBlock Text="{x:Bind ToUpper(ViewModel.Input), Mode=OneWay}" />
    </StackPanel>
</Page>

実行するとこんな感じになります。TextBoxの内容が大文字になって表示されます。

f:id:okazuki:20160804220453p:plain

TwoWayバインディングにも対応しています。例えば表示や入力上は大文字を表示しつつ裏では小文字で管理したいみたいなシナリオ(強引だけどこれくらいしか思いつかなかった)を実現できます。BindBack属性に戻り値を受け取ってプロパティに反映する処理を書きます。BindBackに渡せる関数は引数が1つの関数っぽいです。

例えばこんな感じ。

public class MainPageViewModel : BindableBase
{
    private string input;

    public string Input
    {
        get { return this.input; }
        set { this.SetProperty(ref this.input, value); }
    }
    
    public void ToLower(string x)
    {
        this.Input = x?.ToLower();
    }
}

これでXAMLを以下のようにします。

<Page x:Class="App64.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App64"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Page.DataContext>
        <local:MainPageViewModel />
    </Page.DataContext>
    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBox Text="{x:Bind ToUpper(ViewModel.Input), BindBack=ViewModel.ToLower, Mode=TwoWay}" />
        <TextBlock Text="{x:Bind ViewModel.Input, Mode=OneWay}" />
    </StackPanel>
</Page>

BindBackに先ほど作成したメソッドを設定しています。 実行すると以下のようにTextBoxの中身はフォーカスが外れたタイミングで大文字化されますが、VMのInputプロパティの値は小文字がキープされてることがわかります。

f:id:okazuki:20160804221401p:plain

まとめ

個人的に一番アツイ新機能になります。これを使うとかなり柔軟に値の変換と書き戻しが可能になります。がしがし使っていきたいところですが、アニバーサリーアップデート以降というのがネックになりそうですね。まぁ普通のWindows 10ならアップデートはされるはずなので大きな問題にはならないのかな?

あと、困ったことに、よくデザイナが死にます。