クライアント側のXAML修正・データバインド処理
続いてPage.xamlの編集です。Expression Blendでコントロールを追加しましょう。以下に編集中の画面を示します。
XAMLは次のようになります。
<ComboBox Height="24" Width="112" x:Name="comboBox1" ItemsSource="{Binding}" DisplayMemberPath="Name" SelectionChanged="comboBox1_SelectionChanged" Grid.Row="1" d:LayoutOverrides="Width, Height" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="126.364,0,0,225"/> <StackPanel Height="79" Margin="126,0,0,120" VerticalAlignment="Bottom" Grid.Row="1" x:Name="stackPanel1" Orientation="Vertical" Width="274" HorizontalAlignment="Left" > <StackPanel Height="23" Width="Auto" Orientation="Horizontal" x:Name="stackPanel2"> <TextBlock Text="{Binding Path=Work}" TextWrapping="Wrap" Width="70" Height="Auto" Margin="11,0,30,0" x:Name="textBlock6"/> <CheckBox IsChecked="{Binding Path=Check}" IsEnabled="False" Height="Auto" Width="Auto" Margin="20,0,0,0" Content="チェック" x:Name="checkBox1" /> </StackPanel> <TextBox Height="Auto" Width="Auto" x:Name="textBox1" Text="{Binding Path=Ref}" TextWrapping="Wrap" RenderTransformOrigin="0.5,1.667" Margin="10,20,0,0"/> </StackPanel> <TextBlock x:Name="textBlock1" Height="Auto" Margin="232.364,0,375.636,84" VerticalAlignment="Bottom" Grid.Row="1" TextWrapping="Wrap" ><Run Text="に変更します。"/><LineBreak/><Run Text="よろしければ更新を押してください"/></TextBlock> <TextBlock Height="Auto" HorizontalAlignment="Left" Margin="99.272,0,0,230" VerticalAlignment="Bottom" Width="Auto" Grid.Row="1" Text="名前" TextWrapping="Wrap" x:Name="textBlock4"/> <TextBlock Height="Auto" HorizontalAlignment="Left" Margin="100,0,0,136" VerticalAlignment="Bottom" Width="Auto" Grid.Row="1" Text="内容" TextWrapping="Wrap" x:Name="textBlock2" RenderTransformOrigin="0.545,-0.5"/> <TextBlock Height="Auto" HorizontalAlignment="Left" Margin="99.636,0,0,183" VerticalAlignment="Bottom" Width="Auto" Text="所属" TextWrapping="Wrap" x:Name="textBlock3" RenderTransformOrigin="0.545,-0.5" Grid.Row="1"/> <TextBlock Height="Auto" HorizontalAlignment="Left" Margin="15.728,0,0,230" VerticalAlignment="Bottom" Width="Auto" Text="予定の編集" TextWrapping="Wrap" x:Name="textBlock5" Grid.Row="1"/>
いろいろなコントロールを配置していますが、重要なのは太字でも示した、
- 顧客を選択するためのComboBox
- データバインドをまとめて行うためのStackPanel(2つStackPanelがありますが、stackPanel2は単なるレイアウト用で、重要なのは1つ目のstackPanel1です)
- stackPanel1以下に配置したTextBlock・CheckBox・TextBox
です。
他にもいくつかTextBlockを配置していますが、これらは単純なテキスト表示のためのコントロールでデータバインディングとは関係ありません。また、レイアウトは今回の本論とはあまり関係ありませんので、詳細は上記キャプチャ画面を参考にして設定してください。
さて、バインド関係の記述はVisual Studioに戻ってから行います。以下に設定した主なプロパティ一覧を示します。
コントロール名 | プロパティ | 概要 |
comboBox1 | ItemsSource="{Binding}" | ページ全体のDataContextを利用 |
DisplayMemberPath="Name" | コンボボックスで表示するプロパティを指定 | |
SelectionChanged="comboBox1_SelectionChanged" | イベントハンドラ | |
textBlock6 | Text="{Binding Path=Work}" | DataContextで指定されるオブジェクトのWorkプロパティの値を表示(Visual Studioで追記) |
textBox1 | Text="{Binding Path=Ref}" | DataContextで指定されるオブジェクトのRefプロパティの値を表示(Visual Studioで追記) |
checkBox1 | IsChecked="{Binding Path=Check}" | DataContextで指定されるオブジェクトのCheckプロパティの値(bool値)を表示(Visual Studioで追記) |
続いて、Page.xamlのビハインドコードPage.xaml.csに変更を加えます。以下に追記したコードを示します。
using System.Windows.Browser; //追加したディレクティブ public void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)//comboBox1の選択変更時に呼び出されるイベント { if (comboBox1.SelectedItem != null) { Customer comboData = (Customer)comboBox1.SelectedItem;//選択されたアイテムを取得 stackPanel1.DataContext = comboData; //StackPanel内のDataContextを設定→その下のコントロールにも反映される } }
comboBox1の選択でイベントが発生し、XAML側で設定したとおり、comboBox1_SelectionChanged
メソッドが呼び出されます。ここで、comboBoxで選択した顧客データ(Customerオブジェクト)がstackPanel1のDataContextに設定されます。これによりstackPanel1以下のコントロールのバインド元が設定され、textBox1、TextBlock6、checkBox1にそれぞれ選択した顧客のデータがバインドされます。
この時点でユーザーコントロール全体には、サンプル1で実装したとおり、顧客リスト全体(Customerクラスのリスト)がバインドされており、画面上のDataGridは顧客リスト全部を表示しています。一方で、stackPanel1のDataContextはcomboBox1で選択した顧客1人を表すCustomerクラスが設定されており、stackPanel1以下のコントロールは、この顧客の情報のみを表示しています。1つの画面上で、異なるオブジェクトがバインドされていることに注目してください。
Webサービスへのデータ送信
最後に、更新したデータをWebサーバに送りましょう。サンプル1で記述した、更新ボタンのイベントハンドラを次のように変更します。
private void Button_Click(object sender, RoutedEventArgs e) { if (comboBox1.SelectedItem != null) { Customer comboData = (Customer)comboBox1.SelectedItem;//選択されたアイテムを取得 comboData.Ref = textBox1.Text;//選択されているIDのRefプロパティに入力内容を代入 SetItem(comboData);//内容を引数にメソッドを呼び出す } } public void SetItem(Customer comboData) //サーバへのデータ送信メソッド { string id = comboData.ID;//IDを取得 string re = comboData.Ref;//変更されたRefを取得 var webclient = new WebClient(); webclient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(customer_ChangeStringCompleted); webclient.DownloadStringAsync(new Uri(string.Format("http://localhost:8080/UpdateCustomerRef.aspx?data1={0}&data2={1}", HttpUtility.UrlEncode(id), HttpUtility.UrlEncode(re))));//変更内容をURLに乗せてWebサービスを呼び出す } public void customer_ChangeStringCompleted(object sender, DownloadStringCompletedEventArgs e) { //HTTPリクエスト完了後のイベントハンドラ ReadContent(); //データを再読込する }
textBox1に変更内容を入力し、更新ボタンを押下すると選択されているComboBoxのID値とテキストボックスに入力したテキストがSetItem
メソッドに渡されます。さらにそれらのデータはURL上に乗せられて、XML更新用のサイトに渡されます。
サーバでの処理完了後、クライアント側のイベントハンドラでリスト再読込が実行されます。
では実行してみましょう。顧客リストがDataGridにバインドされるところまではサンプル1と同様ですが、下のコンボボックスに顧客一覧がバインドされています。コンボボックスで顧客名を選択すると、各顧客の所属企業、チェックボックスの内容、予定欄の内容が表示されます。予定を書き換え、更新ボタンを押すとサーバ側に値が渡されてデータが更新されます。DataGrid上の表示も変化し、実際のXMLファイルも書き換わっているはずです。
今回のサンプルはデータバインディングのバインドモードとして、OneTimeモードのみを使用しています。
OneWayモードを使えば、最後のデータ更新をサーバに投げた後、再読込することなく、元のDataGridの予定欄の表示を更新させることができます。また、TwoWayモードを使うと、テキストボックスを更新した地点で、書き換えた内容がオブジェクトに反映されるため、イベントハンドラで明示的にテキストボックスの内容をオブジェクトに書き戻す必要が無くなります。なお、OneWay / TwoWayモードを使うには、バインド元オブジェクトであるCustomer
クラスにINotifyPropertyChangedインターフェイスを実装する必要があります。
Silverlightのようなリッチクライアント環境では、データがローカルにあるのではなく、サーバ側に存在することも多いため、TwoWayモードよりもOneTime / OneWayモードでの実装が自然な場合も多いでしょう。
まとめ
今回は、サーバとXMLデータをやり取りするというモデルを使ってデータバインディングを体験しました。Silverlighでオブジェクトとコントロールのプロパティの連係を簡単に実現できることが実感できたかと思います。最終回の次回はWebサービスについて扱っていきます。ご期待ください。