SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

UWPで追加されたバインディングの新機能~x:Bindマークアップ拡張~

UWPではコンパイル時にバインディングを行うx:Bindマークアップ拡張という機能が加えられました。
今までのBindingマークアップ拡張よりもパフォーマンスが良かったり、コンパイル時の型チェックができたり、と色々メリットがあります。
(ダックタイプ的なバインディングができなくなるので、コンパイル時の型チェックについては若干デメリットともいえる部分もあるかもしれませんが。)

参考資料

MVAの動画&スライド

UWPの各種バインディングについては、
以下のスライドに一度目を通しておくとよいかと思います。

↓のページの「04 | XAML Data binding」のスライド
http://www.microsoftvirtualacademy.com/training-courses/a-developers-guide-to-windows-10?prid=mvp-5001048

x:Bindの使い方

基本は今まであった{Binding }マークアップ拡張と同じ感じ。
f:id:minami_SC:20150827134222p:plain

通常のBindingとの違いは以下のような点となります。

  1. デフォルトではOneTimeバインディング
    1. OneWay, TwoWayバインディングも使える。その場合には明示的にモード指定する。
  2. バインディング時のソースはViewクラス自身。コードビハインドに定義した各種プロパティにバインドできる。
    1. ElementName, Source, RelativeSourceなどを使ったソースの指定はできない。
    2. DataContextとしてViewModelを設定したい場合は、コードビハインドにViewModelのプロパティを追加しこれにバインドする。
  3. UpdateSourceTriggerない
  4. DataTemplate内で使うときは、x:DataTypeを使って、データの型を指定する必要がある。
    1. コンパイルバインディングと言っても、DateTemplateみたいな実行時に動的にインスタンスが設定されるものの中でも使えます。ただし、コンパイラが型を知るために、x:DataTypeでの型指定が必要です。
  5. イベントにもバインドできる!!
    1. 各種イベント発生時に呼び出されるメソッドをx:Bindで設定できる

こんな違いがあります。
逆に言えば、これら以外は一緒なので、今までのBindingとほぼ同じ感覚で使えます。


Pageクラス実装の常套句

x:Bindではコードビハインドがバインディングソースとなります。
そのため、MVVM的にVMのプロパティとバインディングしたいという場合には、以下のようなコードがUWPでのPageクラスの常套句になりそうです。


コードビハインドにViewModelプロパティを作成し、DataContext変更時には、ViewModelプロパティが追従するようにします。

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            // DataContext変更時にViewModelプロパティの値が変わるように
            // イベントハンドラの設定
            this.DataContextChanged += (s, e) =>
            {
                ViewModel = this.DataContext as MainPageViewModel;
            };
        }

        public MainPageViewModel ViewModel { get; set; }
    }

こんな風にバインディングするイメージですね。
f:id:minami_SC:20150827134232p:plain



毎回繰り返し書く共通のコードになりそうなんで、共通の基底クラスとか作れそう。
だけど、XAML上に書く要素については、個人的にはあんまり継承ベースなコーディングをしたくないのが悩みどころ。。

イベントへのバインディング

このx:Bind。XAML上でイベントとメソッドをバインドすることもできます。
すげぇ!!メッチャ便利!!
f:id:minami_SC:20150827134249p:plain


ICommandを実装したコマンドや、EventToCommandなどの置き換えとして使えるようになります。
ただし、Commandバインディングと違い、イベントのバインディングでは以下の制限があります。
・任意のパラメータを含めることができない
・CanExecuteによる実行可否の制御ができない。

CanExecuteの件があるので、アプリ内でいろんな箇所から操作するようなものは、今までどおりICommandの実装をして、っていう流れになるかもしれませんね。


以前、こんなマークアップ拡張を使って、WPF上で同じようなことできるようにしてましたが、
こんなの作らなくても、標準の機能でできるようになる上、コンパイルバインディングのため、パフォーマンス上も有利。
いいことずくしですね!!


x:Bindが向かない部分

x:Bindも万能というわけではなく、以下のようなケースにはx:Bindは向かないです。

  1. ダックタイピング的なバインディング
    1. Text="{Binding Age}" みたいに書いておいて、この時のDataContextはAgeというプロパティを持っている○○という型になったり、△△という型になったり、という場合
  2. JSONデータのような型のないデータをバインドする場合
  3. コードビハインドで、動的にバインディングを設定したり解除したりする場合
  4. スタイル中で使う場合
    1. x:BindはStyle内のSetterで使えないとのこと

使ってみる

前回のサンプルをそのままx:Bindで書き直してみました。
コードビハインドにViewModelのプロパティを作ったり、バインディングのMode指定を一部付け加えたりしたくらい。
(あと、前回のサンプルはMainPageViewModelクラスの定義がpublicになってなかったので、publicに直してます。MainPageクラス内でpublicなプロパティとして定義できるようにするため。)

MainPage.xaml
<Page x:Class="App1.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:local="using:App1"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Page.DataContext>
        <local:MainPageViewModel />
    </Page.DataContext>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBox Width="300"
                 Margin="10"
                 HorizontalAlignment="Left"
                 VerticalAlignment="Top"
                 Text="{x:Bind ViewModel.Name, Mode=TwoWay}"
                 TextWrapping="Wrap" />
        <Button Margin="315,10,0,0"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Command="{x:Bind ViewModel.ShowMessageCommand}"
                Content="メッセージを表示" />
        <TextBlock Margin="10,47,0,0"
                   HorizontalAlignment="Left"
                   VerticalAlignment="Top"
                   Text="{x:Bind ViewModel.Message, Mode=OneWay}"
                   TextWrapping="Wrap" />
    </Grid>
</Page>
MainPage.xaml.cs
    /// <summary>
    /// それ自体で使用できる空白ページまたはフレーム内に移動できる空白ページ。
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            // DataContext変更時にViewModelプロパティの値が変わるように
            // イベントハンドラの設定
            this.DataContextChanged += (s, e) =>
            {
                ViewModel = this.DataContext as MainPageViewModel;
            };
        }

        public MainPageViewModel ViewModel { get; set; }
    }
MainPageViewModel.cs
    public class MainPageViewModel : BindableBase
    {
        private string name;
        public string Name
        {
            get { return name; }
            set { this.SetProperty(ref this.name, value); }
        }

        private string message;
        public string Message
        {
            get { return message; }
            set { this.SetProperty(ref this.message, value); }
        }

        private RelayCommand showMessageCommand;
        public RelayCommand ShowMessageCommand
        {
            get { return showMessageCommand = showMessageCommand ?? new RelayCommand(ShowMessage); }
        }
        private void ShowMessage()
        {
            // テキストボックスに表示するメッセージを設定します。
            this.Message = string.Format("こんにちは。{0}さん", this.Name);
        }
    }