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 }マークアップ拡張と同じ感じ。
通常のBindingとの違いは以下のような点となります。
- デフォルトではOneTimeバインディング
- OneWay, TwoWayバインディングも使える。その場合には明示的にモード指定する。
- バインディング時のソースはViewクラス自身。コードビハインドに定義した各種プロパティにバインドできる。
- ElementName, Source, RelativeSourceなどを使ったソースの指定はできない。
- DataContextとしてViewModelを設定したい場合は、コードビハインドにViewModelのプロパティを追加しこれにバインドする。
- UpdateSourceTriggerない
- DataTemplate内で使うときは、x:DataTypeを使って、データの型を指定する必要がある。
- イベントにもバインドできる!!
- 各種イベント発生時に呼び出されるメソッドを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; } }
こんな風にバインディングするイメージですね。
毎回繰り返し書く共通のコードになりそうなんで、共通の基底クラスとか作れそう。
だけど、XAML上に書く要素については、個人的にはあんまり継承ベースなコーディングをしたくないのが悩みどころ。。
イベントへのバインディング
このx:Bind。XAML上でイベントとメソッドをバインドすることもできます。
すげぇ!!メッチャ便利!!
ICommandを実装したコマンドや、EventToCommandなどの置き換えとして使えるようになります。
ただし、Commandバインディングと違い、イベントのバインディングでは以下の制限があります。
・任意のパラメータを含めることができない
・CanExecuteによる実行可否の制御ができない。
CanExecuteの件があるので、アプリ内でいろんな箇所から操作するようなものは、今までどおりICommandの実装をして、っていう流れになるかもしれませんね。
以前、こんなマークアップ拡張を使って、WPF上で同じようなことできるようにしてましたが、
こんなの作らなくても、標準の機能でできるようになる上、コンパイル時バインディングのため、パフォーマンス上も有利。
いいことずくしですね!!
使ってみる
前回のサンプルをそのまま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); } }