テキストボックスをバインドして入力中にロジックを動かすには?[Windows 8.1ストアアプリ開発]:WinRT/Metro TIPS
例えば検索画面で、検索文字列を1文字入力するごとに検索結果の表示を絞り込んでいきたい場合など、テキストボックスに入力中の文字列をバインド元に反映させて処理する方法を説明する。
powered by Insider.NET
テキストボックスに入力中の文字列をバインド元に反映させて処理をさせたいことはないだろうか? 例えば、検索する画面で、検索文字列を1文字入力するごとに検索結果の表示を絞り込んでいきたい場合などだ。Windows 8(以降、Win 8)のWindowsストアアプリでは、それはバインディングでは実現できなかった。本稿では、Win 8でそれを実現していた方法を振り返り、Windows 8.1(以降、Win 8.1)での新しい方法を2通り紹介する。なお、本稿のサンプルは「Windows Store app samples:MetroTips #65(Windows 8.1版)」からダウンロードできる。
事前準備
Win 8.1用のWindowsストアアプリを開発するには、Win 8.1とVisual Studio 2013(以降、VS 2013)が必要である。本稿ではOracle VM VirtualBox上で64bit版Windows 8.1 Pro(日本語版)とVisual Studio Express 2013 for Windows(日本語版)*1を使用している。
*1 マイクロソフト公式ダウンロードセンターの「Microsoft Visual Studio Express 2013 for Windows」から無償で入手できる。
ベースとなるプログラムを用意する
これから行う実験のベースとなるプログラムを作っておく。本稿ではXAMLコードの詳しい説明は省略させていただくので、次の画像を参考にして適宜UIを構築してほしい。
サンプルコードを実行している画面
これは、別途公開しているサンプルコードを実行したときの画面である。 左側はWin 8のときの方法で実装してある。1段目の入力欄に文字列を入力している最中は、2段目のバインドしたテキストボックスには反映されない(入力欄からフォーカスを移動したタイミングで反映される)。3段目のテキストボックスの内容は、1段目の入力欄のTextChangedイベントで書き換えている。なお、Win 8.1で追加された方法を使う(最下段のトグルスイッチをオンにする)と、イベントハンドラー内で強制的にバインディングソースへ変更を反映できるので、2段目のテキストボックスにも1文字入力するごとに反映されるようになる。 右側はWin 8.1で新しく追加された方法で実装した。上段の入力欄に文字列を入力している最中に、下段のバインドしたテキストボックスに反映される。
ベースとなるプログラムでは、バインディングソースとなるstringオブジェクトを用意し、TextBoxコントロール(Windows.UI.Xaml.Controls名前空間)をそれにバインドする(次の図)。
stringオブジェクトとTextBoxコントロールをバインドする
一方のTextBoxコントロールは双方向バインディングとし、他方は単方向バインディングとする。 これにより、上の[入力]というヘッダーが付けてあるTextBoxコントロールに文字列を入力すると、下の[バインド]というヘッダーを付けたTextBoxコントロールに反映されるはずである。
実装は以下のように進める。まずWindowsストアアプリのプロジェクトを作るときに、[新しいアプリケーション (XAML)]を選ぶ。出来上がったプロジェクトから「MainPage.xaml」ファイルを削除し、あらためて[基本ページ]を「MainPage.xaml」ファイルとして追加する。これにより、プロジェクトにCommonフォルダーとその中にソースコードが幾つか自動生成される。これは、以降でデータバインディングの実装を簡単に行うために必要だ。
次に、「MainPage.xaml」ファイルにTextBoxコントロールを2つ配置し、データバインディングを設定する(次のコード)。
<TextBox Header="入力" ……省略……
Text="{Binding Path=SampleText1, Mode=TwoWay}" />
<TextBox Header="バインド" ……省略……
Text="{Binding Path=SampleText1}" />
上のTextBoxコントロールは双方向バインディング、下は単方向バインディングだ。バインディングソースに指定している「SampleText1」は、次に説明する。 別途公開しているサンプルコードでは、このコードは画面左側の1段目と2段目のTextBoxコントロールに当たる。
最後に、コードビハインドでバインディングソースの初期値を設定しておく(次のコード)。
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
this.DefaultViewModel["SampleText1"] = "TEST1";
}
Private Sub NavigationHelper_LoadState(sender As Object, e As Common.LoadStateEventArgs)
Me.DefaultViewModel("SampleText1") = "TEST1"
End Sub
太字の部分を追加する。「SampleText1」というキーで文字列をセットした。
これで実行してみると、[入力]欄に入力している最中には[バインド]欄に反映されないことが確認できる。TextBoxコントロールとのデータバインドでは、1文字入力するごとにはバインディングソースへ反映されないのだ。[入力]欄からフォーカスを移動したときだけ反映されるのである。
イベントでロジックを呼び出す方法
Win 8では、1文字入力するごとに処理するには、以下のようにしてTextChangedイベントでロジックを呼び出すしかなかった。
データバインディングはあきらめて、入力用のTextBoxコントロールにはTextChangedイベントを追加し、出力用のTextBoxコントロールには名前を付ける(次のコード)。
<TextBox TextChanged="inputText1_TextChanged" Header="入力" ……省略…… />
<TextBox x:Name="outputText1" Header="イベント" ……省略…… />
太字の部分を追加した。
別途公開しているサンプルコードでは、このコードは画面左側の1段目と3段目のTextBoxコントロールに当たる。なお、サンプルコードでは、データバインディングの設定を残している(ここでは不要なのだが、次に説明する方法では必要になる)。
次に、イベントハンドラーでロジックを呼び出して、結果を出力用のTextBoxコントロールに書き出すようにする(次のコード)。
private void inputText1_TextChanged(object sender, TextChangedEventArgs e)
{
var input = (sender as TextBox).Text;
// 本来はここでinputを使ってロジックを呼び出す
this.outputText1.Text = input;
}
Private Sub inputText1_TextChanged(sender As Object, e As TextChangedEventArgs)
Dim input = CType(sender, TextBox).Text
' 本来はここでinputを使ってロジックを呼び出す
Me.outputText1.Text = input
End Sub
これで実行してみると、確かに1文字入力するごとに反映される。
しかし、実際に開発する場面を考えてみると、他のところではデータバインディングで画面とロジックをつないでいるのに、テキストボックスだけはコードビハインドからロジックを呼び出すというのでは、画面とロジックのインターフェースがバラバラである。以降で説明するように、Win 8.1では、この問題は簡単に解決できるようになった。
イベントで強制的に反映させる方法
Win 8.1では、新設されたBindingExpressionクラス(Windows.UI.Xaml.Data名前空間)を使うことで、コントロールの内容を強制的にバインディングソースへ反映できるようになった。
まず、最初のXAMLコードに対して、データバインディングを残したままで、入力用のTextBoxコントロールにTextChangedイベントを追加する(次のコード)。
<TextBox TextChanged="inputText1_TextChanged" Header="入力" ……省略……
Text="{Binding Path=SampleText1, Mode=TwoWay}" />
<TextBox Header="バインド" ……省略……
Text="{Binding Path=SampleText1}" />
入力用のTextBoxコントロールにTextChangedイベントを追加した(太字の部分。追加する内容自体は上で示したXAMLと同様である)。
別途公開しているサンプルコードでは、このコードは画面左側の1段目と2段目のTextBoxコントロールに当たる。
そして、[入力]用のTextBoxコントロールの内容をイベントハンドラーで強制的にバインディングソースへ反映させるようにするのだ(次のコード)。
private void inputText1_TextChanged(object sender, TextChangedEventArgs e)
{
// BindingExpressionを使って強制的にバインディングソースへ反映させる
var be = (sender as TextBox).GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
Private Sub inputText1_TextChanged(sender As Object, e As TextChangedEventArgs)
' BindingExpressionを使って強制的にバインディングソースへ反映させる
Dim be = CType(sender, TextBox).GetBindingExpression(TextBox.TextProperty)
be.UpdateSource()
End Sub
別途公開しているサンプルコードでは、トグルスイッチによってこの処理をオン/オフできるようにしている。
これでTextBoxコントロールに1文字入力するごとに、その内容がバインディングソース(を介してバインディング先のコントロール)に反映されるようになるので、確かめてほしい。
この方法で画面とロジックのインターフェースをデータバインディングに統一できるのだが、実際には次に説明する方法の方が簡単だ。このBindingExpressionを使って強制的にバインディングソースへ反映させる方法は、むしろユーザーアクション(ボタンのタップなど)に基づいてバインディングソースへ反映させたいときに有効である。
UpdateSourceTriggerプロパティを使う方法
さらに、Win 8.1でBindingクラス(Windows.UI.Xaml.Data名前空間)に追加されたUpdateSourceTriggerプロパティを使えば、イベントに頼らずに済むようになる。このプロパティに「PropertyChanged」と設定するだけである(次のコード)。
<TextBox Header="入力" ……省略……
Text="{Binding Path=SampleText1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Header="バインド" ……省略……
Text="{Binding Path=SampleText1}" />
最初のコードに太字の部分を追加する(TextChangedイベントは付けない)。
別途公開しているサンプルコードでは、このコードは画面右側の1段目と2段目のTextBoxコントロールに当たる。なお、サンプルコードでは、バインディングソースを「SampleText1」から「SampleText2」に変えている。
なお、UpdateSourceTriggerプロパティの既定値は「Default」であり、その動作はMSDNには「PropertyChangedの値と同じ値として評価されます」と記述されているが、TextBoxコントロールにおいては誤りである。TextBoxコントロールのバインディングで「Default」を指定すると、WPFで「LostFocus」を指定したときと同じ動作(=フォーカスを失ったときに反映)になる(ただし、WindowsストアアプリのUpdateSourceTriggerプロパティには「LostFocus」を指定できない)。
まとめ
Win 8.1でテキストボックスに入力中の文字列をバインディングソースに反映させるには、バインディングの指定に「UpdateSourceTrigger=PropertyChanged」を追加すればよい。また、ユーザーアクションのタイミングでバインディングソースへ反映させるには、BindingExpressionクラスを利用する。
BindingExpressionクラスについては、次のドキュメントも参照してほしい。
UpdateSourceTriggerプロパティについては、次のドキュメントも参照してほしい。
- MSDN:Binding.UpdateSourceTrigger Property
- MSDN:UpdateSourceTrigger Enumeration
- MSDN:UpdateSourceTrigger 列挙体(WPFの解説、「Explicit」の使い方などがよく分かる)
Copyright© Digital Advantage Corp. All Rights Reserved.