データバインディングの概要
データバインディングとは、ユーザーインターフェイスのプロパティと他のコントロールやクラスのプロパティを関連づける処理のことです。データバインディングは主に、データベースやWebサービスなどから取得したリソースを、クライアント上のコントロールに表示する場合などに利用します。
データバインディングを使わない場合は、「リソースを順に取得しながら、対応するコントロールのプロパティに代入する」といったコードが必要になります。例えば、次のようなコードになります。
var resource = getResource(); //データベースやWebサービスなどからリソースを取得 nameTextBox.Text = resource.Name; //テキストボックスに値を代入 addressTextBlock.Text = resource.Address; //テキストブロックに値を代入
一方、データバインディングを使えば、「このコントロールには○○というフィールドを表示させる」という設定をしておくだけで、実際の値の割り当て(=バインディング)は自動的に行われます。詳細は後で扱いますが、データバインディングがどれぐらい簡潔な記述でできるかを少し見てみましょう。XAMLコード側では次のようにコントロールと、割り当てるプロパティを関連づけます。
<TextBox Text="{Binding Path=Name}" x:Name="nameTextBox"/> <TextBlock Text="{Binding Path=Address}" x:Name="addressTextBlock"/>
合わせて分離コードで、バインドするオブジェクトを指定します。
var resource = getResource(); //データベースやWebサービスなどからリソースを取得 this.DataContext = resource; //バインド元オブジェクトを設定
バインドするコントロールが少ない場合は、データバインディングを使わないコードでも対応できますが、多数のコントロールを使うケースや、データ数に応じてコントロールの数を増減させる場合などは、データバインディングを使った方がずっと楽になります。
特に、業務アプリケーションの多くは、一般に帳票画面と呼ばれるリソースを表示・編集する画面の集合でできており、この実装においてデータバインディングは欠かせない技術と言えます。
.NET Framework 3.5のWPFは強力なデータバインディングをサポートしていますが、そのサブセット的位置づけにあるSilverlight 2もやはり多彩なデータバインディングに対応しています。本稿ではSilverlight 2のデータバインディングの概要と業務アプリケーションへの実装について扱います。
バインド元オブジェクト
Silverlight 2におけるバインド元のオブジェクトですが、SOAP、RSS、Webサービスを経由したXMLサービスやデータベースなどさまざまなものが使えます。各種データソースをNET Framework3.5の新機能であるLINQを利用して、加工してからバインドすることも可能です。また、コード内に実データを埋め込んでおき、それをバインド元にすることもできます。
Silverlight 2のデータバインドモード
Silverlight 2のデータバインディングには次の3つのモードがあります。
モード | 概要 |
OneTime | バインドされた時点のオブジェクトの値がコントロールに反映される。以後、オブジェクトの値が更新されても、コントロールは更新されない。 |
OneWay | バインドされた時点のオブジェクトの値がコントロールに反映され、以後、オブジェクトの値が更新されるたびに、コントロールが更新される。 |
TwoWay | OneWayに加え、コントロールの値が更新された場合にも、オブジェクトの値に反映される。 |
OneTimeバインディングは固定値をバインドするモードです。バインド先でデータソースが指定された時点で一度だけバインドが実行されます。バインド後に元データが変化しても、コントロールには反映されません。データソースの更新がほとんど生じない場合はOneTimeバインドを使用するのが良いでしょう。
OneWayバインディングはその名の通り片方向からのバインドモードで、バインド元データソースの変化にリアルタイムに対応し、バインド先の表示データが追随する形で動作します。
TwoWayバインディングは双方向のバインドモードです。バインド元またバインド先双方からのデータ更新機能を実装したい場合に使用します。ちなみに、WPFのデータバインドではデフォルトのモードがTwoWayになっていましたが、SilverlightではデフォルトがOneWayになります。この点、WPFでの実装経験のある開発者は注意が必要です。
なお、バインドモードとしてOneWayおよびTwoWayを使う場合には、バインドするオブジェクトはINotifyPropertyChangedインターフェイスを実装する必要があります。これはオブジェクトの値が更新されたことを検出するためのインターフェイスです。
Silverlight 2のバインド先とDataContext
Silverlight 2では、実質的にほとんどのコントロールのプロパティをデータバインディングの対象とすることが可能です。TextBlockコントロールのText
プロパティのような、画面に表示されるプロパティにバインドするのが一般的ですが、スタイル指定のプロパティや、コントロールの位置にオブジェクトの値をバインドすることも可能です。
バインディングを行う場合はXAMLでユーザーインターフェイスを定義する際に、{Binding …}
のような特殊な構文を使用します。例えば、TextBlockコントロールのText
プロパティにバインディングで値を設定する場合は下のように記述します。
<TextBlock Text="{Binding Path=FirstName Mode=OneWay}" />
この場合、Text
プロパティに、バインド元オブジェクトのFirstName
プロパティの値がOneWayモードでバインディングされます。
さて、今の構文においては、プロパティの種類のみが指定され、バインド元オブジェクトが明示されていないことに気づかれたかと思います。Silverlightのデータバインドは、コントロールごとに特定のバインド元オブジェクトを指定することもできますが、特に指定しない場合は親コントロールのDataContext
プロパティで指定されたオブジェクトが継承されます。下にDataContext
プロパティの設定例を示します。
object1.FirstName = "Hello,World!"; this.DataContext = object1;
この記述の場合、バインド元オブジェトとしてobject1が指定されています。従って、先ほどのTextBlockにバインドされるのはobject1のFirstName
プロパティ、つまり、"Hello,World!"という文字列になります。もちろんこの例ではobject1はFirstName
プロパティを持つクラスのインスタンスでなければなりません。
前述の通り、ユーザーコントロールのDataContext
プロパティはそのユーザーコントロール以下に配置されたコントロールに継承されます。例えばGridやStackPanelといったレイアウト系のコントロールにDataContextを設定すると、それら以下のコントロールにも同様に設定したDataContextが適用されます。
一方、データソースを指定してバインドする場合は次のように記述します。
<TextBlock x:Name="textBlock" Text="{Binding Source={StaticResource bindSource} }" /> <!-- //Text属性に固定ソースを指定してバインディング -->
public Page() { InitializeComponent(); string firstName= "Hello,World!"; Binding bindSource = new Binding(); bindSource.Source = firstName; //バインド先に対応した型のソースを指定 textBlock.SetBinding(textBlock.TextProperty, bindSource); }
ここでは、バインド情報bindSourceにテキストデータを設定し、XAML側で固定ソースとしてバインドしています。
データバインディングに使われる代表的なコントロール
先述しましたが、Silverlightのバインド先には実質的に、ほとんどのコントロールを指定することができます。とはいえ、業務アプリケーションにおいては、頻繁に利用されるコントロールは自ずと限定されてきます。以下に一般的な業務アプリケーションにおいてバインド先としての利用が想定される代表的なコントロールを紹介します。
DataGrid
DataGridは行と列で表示可能なコントロールで、列の自動生成や表示カスタマイズ機能などを備えています。表形式でデータを扱うことの多い業務アプリケーションにおいて、使い勝手のよいコントロールです。
DataGridは自動列生成機能を持つため、データをバインドするだけで、自動的にMicrosoft Excelで作成するような表を出力できます。また、表示のカスタマイズ機能を使えば希望するスタイルでデータを表示できます。表示をカスタマイズする場合、各列にはバインド元オブジェクトのプロパティに対応するテキストブロックやチェックボックスを配置することができます。また、行・列ごとのテンプレートを指定することにより、さらに詳細なカスタマイズを行うこともできます。
本稿ではサンプルで実際にDataGridを使ったデータバインドを扱います。
ListBox
複数のアイテムを表示するボックスとして用います。テキストや他のコントロールなどを一覧表示し、選択させることもできます。また、テンプレートを指定することによりListBoxの中にStackPanelなどのレイアウトコントロールを配置することもできます。
ComboBox
前回のサンプルでも扱いました。複数のアイテムを表示するボックスです。プロパティや扱えるオブジェクトの種類などの特性はListBoxとほとんど同じです。ComboBoxの場合、データはボックス内にドロップダウンメニューとして表示されます。
TextBox、TextBlock
前回のサンプルでも扱った、テキスト表示のための基本的なコントロールです。データバインドにより、テキストの内容やフォントサイズ、フォントカラーなどのスタイルに関するものも含め、さまざまなプロパティにオブジェクトの値を設定することができます。
サンプル1:DataGridを使った顧客リストの取得
では実際にサンプルを通してSilverlightのデータバインディングを体験してみましょう。サンプル1ではデータバインディングによりWeb上の顧客リストをXML形式で取得し、DataGrid上に表示します。
以下に成功時の画面を示します。
サンプル1の構成
サーバからXML形式のデータを非同期的に取得し、LINQ to XMLを使用してデータを加工したうえでDataGridにバインドします。
以下に簡単な構成図を示します。
以下に作成するファイルを示します。
サーバ/クライアント | ファイル名 | 概要 |
クライアント側(DataBindApplicationプロジェクト) | Page.xaml | メイン画面を定義 |
Page.xaml.cs | メイン画面のビハインドコード | |
Customer.cs | XMLの各要素をプロパティに持つクラス | |
サーバ側(DataBindApplication.Webプロジェクト) | /App_Data/Customer.xml | 顧客リストの実体 |
Default.aspx | XML形式のデータを返すページ(Silverlightからはこのサービスにアクセスする) | |
Default.aspx.cs | ビハインドコード |
ではサンプルの作成に移りましょう。Visual Studio 2008で新しいSilverlightプロジェクトを作成します。今回のサンプルプロジェクトの名称は「DataBindApplication1」としました。
顧客データWebサービスの作成
まず、サーバ側から作っていきましょう。この部分はSilverlight 2とは直接関係ありませんので、コード自体の詳細な説明は行いません。
最初に、顧客リストのデータソースであるXMLファイルを準備します。ソリューションエクスプローラで[ソリューション]-[DataBindApplication.Web]-[App_Data]から右クリックし、[追加]-[新しい項目]を選択します。[テンプレート]で[XMLファイル]を選び「Customer.xml」と名前を付けて[追加]ボタンを押下します。ソリューション上にCustomer.xmlが追加されます。
Customer.xmlを右クリックから開きメインウィンドウで編集します。サンプルでは次のようなデータソースを使用します。内容は適当に準備したものです。各顧客(Item要素)にID、名前(Name要素)、所属企業(Work要素)、メールアドレス(Mail要素)、チェック欄(Check要素) 、そして予定欄(Ref要素)のフィールドを設定しました。ここには3件分のリストだけ示しますが、サンプルファイルには8件のデータが入っています。
<?xml version="1.0" encoding="utf-8"?> <Customer> <Item> <ID>1001</ID> <Name>佐藤裕</Name> <Work>赤井商事</Work> <Mail>mailto:sato@***.com</Mail> <Check>false</Check> <Ref>14日プレゼン</Ref> </Item> <Item> <ID>1002</ID> <Name>田中浩治</Name> <Work>青田産業</Work> <Mail>mailto:tanaka@***.com</Mail> <Check>false</Check> <Ref>札幌出張</Ref> </Item> <Item> <ID>1003</ID> <Name>高橋学</Name> <Work>白石興産</Work> <Mail>mailto:takahashi@***.com</Mail> <Check>true</Check> <Ref>明日来社</Ref> </Item> ...以下省略...
顧客リストを収めたXMLファイルが作成できましたので、続いてサーバ上にASP.NETで簡易XMLサービスを立ち上げます。
プロジェクトを作成するとDataBindApplication1.Webソリューション上に自動的に「Default.aspx」ファイルが作成されますが、今回はこのファイルに追記してXMLサービスを提供しましょう。書き換えるのはビハインドコードのみです。ソリューションエクスプローラで「Default.aspx」のビハインドコード「Default.aspx.cs」を右クリックし[開く]を選択しましょう。以下に追記後のコードを示します。
using System.IO; //usingディレクティブを追加する using System.Text; //usingディレクティブを追加する namespace DataBindApplication1.Web { public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Response.ContentType = "text/xml"; Response.Output.Write(File.ReadAllText(Server.MapPath("~/App_Data/Customer.xml"), Encoding.UTF8)); Response.End(); } } }
コードの詳細は省略しますが、App_Data/Customer.xmlファイルをレスポンスとして出力しています。実際にソリューションエクスプローラで「Default.aspx」を右クリックし[ブラウザで表示]を選択すると、ブラウザ上に作成したXMLファイルが表示されるはずです。
サーバサイドでもう一つ作業があります。クライアントのみで動作するアプリケーションであれば、サーバ側プロジェクトで使用するポートを意識する必要はありませんが、サンプルでは、クライアント側からテストサーバ側に随時アクセスしてデータを取得するので、ポートを明示する必要があります。[ソリューション]-[DataBindApplication.Web]を右クリックして[プロパティ]を選択します。[Web]タブの中程から下あたりに[VisualStudio開発サーバーを使用する]のラジオボタンがありますが[ポートを指定する]に変更しましょう。何番ポートでも構いませんが、今回は8080としました。クライアントサイドからテストサーバにアクセスする際にはこのポートを利用します。もちろんこれはテスト環境での設定ですので、実際にWeb上でサービスを提供する場合はサーバの構成に合わせてポートを指定する必要があります。
以上で、簡易Webサービスの実装は完了です。
クライアント側実装 DataGridコントロールの自動列生成のテスト
続いてクライアント側実装に移りましょう。Page.xamlをExpression Blendで編集します。デフォルトでは参照情報が足りないためDataGridコントロールが利用できません。Expression BlendでDataGridを利用するため、Visual Studio側で参照情報を設定しておきます。Visual Studioのソリューションエクスプローラで[参照設定]を右クリックし[参照の追加]でSystem.Windows.Controls.Dataを追加しておきましょう。これでDataGridコントロールが有効になります。また、DataGridコントロールとは関係ありませんが、後で使用するので、ついでにSystem.Xml.Linqの参照も追加しておきます。
編集時の画面は次のとおりです。
上段グリッドにTextBlockを配置し下段グリッドにはアセットライブラリからDataGridとButtonをドロップします。それぞれマウスで適当な位置に配置し、大きさなどのスタイルを整えます。
DataGridに設定するプロパティですが、AutoGenerateColumns
プロパティにTrueを設定しました。これはバインドするデータによって自動的に列を生成する機能です。まずはこの機能を利用してDataGridの基本的な動作を試してみましょう。
バインドに関する設定を行うには、プロパティウィンドウからItemSourceのボックスをクリックします。以下の画面が出現します。
メニューから[カスタム式]を選択します。ここに「{Binding}」とだけ入力します。今回、データソースを指定するDataContext
プロパティはビハインドコードで設定しますのでPage.xaml側に記述するのはこれだけで十分です。
さらにUserControlには読み込み時に発生するLoadedイベントのハンドラを設定しました。
以下に設定した主なプロパティの一覧を示します。
コントロール名 | プロパティ | 概要 |
UserControl | Loadedイベント | ユーザーコントロール読み込み時に発生するイベントのハンドラ。WebサービスからXML形式の顧客リストを読み込む機能を実装する |
DataGrid | AutoGenerateColumns | 読み込んだデータに応じて列を自動的に生成するプロパティ |
ItemsSource | バインド情報を設定 | |
IsReadOnly | 読み取り専用の属性を設定 | |
Button | Clickイベント | ページ更新のハンドラ |
実際のXAMLコードは次のとおりです。
<UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="DataBindApplication1.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="800" Height="600" Loaded="UserControl_Loaded" xmlns:DataBindApplication1="clr-namespace:DataBindApplication1" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition Height="0.133*"/> <RowDefinition Height="0.867*"/> </Grid.RowDefinitions> <TextBlock HorizontalAlignment="Left" Margin="48,24,0,32" Width="Auto" Text="顧客リスト" TextWrapping="Wrap" FontSize="16"/> <data:DataGrid Margin="48,48,248,192" Grid.Row="1" x:Name="list1" AutoGenerateColumns="True" ItemsSource="{Binding}" IsReadOnly="False" /> <Button HorizontalAlignment="Right" Margin="0,0,248,154" VerticalAlignment="Bottom" Grid.Row="1" Content="更新" Click="Button_Click" RenderTransformOrigin="0.429,-1.182" Width="48"/> </Grid> </UserControl>
続いて、XMLの顧客リストの各要素をプロパティに持つCustomer
クラスを定義します。実際のデータバインドは、このCustomer
クラスのリストをバインド元オブジェクトにすることになります。ソリューションエクスプローラのクライアント側[DataBindApplication1]で右クリックし[追加]-[新しい項目]-[クラス]でCustomer.csを作成します。以下にコードを示します。
namespace DataBindApplication1 { public class Customer { public string ID { get; set; } public string Name { get; set; } public string Work { get; set; } public string Mail { get; set; } public bool Check { get; set; } public string Ref { get; set; } } }
ちなみに、プロパティの定義に利用したのはC# 3.0からの機能である自動プロパティです。これはプロパティの実体となる変数を定義することなくプロパティを使用できる便利機能です。
Customer
クラスは前述の顧客リストXMLのItem要素に対応し、Item要素以下の子要素に対応するプロパティを持ちます。後述するように、サーバから取得したXMLをLINQでこのCustomer
クラスに変換し、データソースとします。
最後にPage.xamlのビハインドコード、Page.xaml.csを作成します。
using System.Xml.Linq; //usingディレクティブを追加 ...中略... private void UserControl_Loaded(object sender, RoutedEventArgs e) { ReadContent(); //ユーザーコントロールがロードされた時にデータを読み込む } public void customer_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { //Webサービスからのデータ取得が終わった時の処理 XDocument Xmllist = XDocument.Parse(e.Result); //取得した文字列をXML文書にして代入 var customers = from customer in Xmllist.Descendants("Item") select new Customer //LINQを使ってXMLの各要素を新しいCustomerオブジェクトとして出力 { ID = (string)customer.Element("ID"), Name = (string)customer.Element("Name"), Work = (string)customer.Element("Work"), Mail = (string)customer.Element("Mail"), Check = (bool)customer.Element("Check"), Ref = (string)customer.Element("Ref") }; this.DataContext = customers ; //ユーザーコントロール全体に適用されるDataContextを設定 } public void ReadContent() //ページロードおよび更新ボタン共通のイベント { WebClient customer = new WebClient(); //HTTPリクエストを行うクラス customer.DownloadStringCompleted += new DownloadStringCompletedEventHandler(customer_DownloadStringCompleted); //ダウンロード完了時のイベントハンドラを設定 customer.DownloadStringAsync(new Uri("http://localhost:8080/Default.aspx")); //非同期的にサーバ側に作成したサービスにアクセス } private void Button_Click(object sender, RoutedEventArgs e) { ReadContent(); //更新ボタンが押された際にもデータを読み込む }
まず、LINQを使うのでusingディレクティブに「System.Xml.Linq;」を追加します。LINQに関しては本論から離れるので詳述しませんが、
from (クエリの中でのみ有効な一時変数)in (データソース)select (出力形式)
という形式で記述しています。ここでは取得したXMLのItem要素を順にcustomer
という変数に取り出し、各要素をプロパティに変換しながらCustomer
クラスに変換し、Customer
クラスの集合をcustomers
変数に代入しています。LINQについては連載第4回も併せて参照してください。
他の各部の動作についてはコメントを参照いただくとして、ここではバインディングに関連する部分に注目してみます。
this.DataContext = customers;
この記述でユーザーコントロール全体のバインド元オブジェクトとして、customers
変数が設定されます。この結果XAMLファイル内で特にソースを明示せずデータバインディングを実行した場合、ここで設定したDataContextが継承されます。
では、ここまででとりあえず実行してみましょう。
サーバ上のXMLデータをダウンロードし、Customer
クラスのオブジェクトに変換したものを、DataGridにバインドして表示しています。boolean型の変数は自動的にCheckBoxで、それ以外の要素はTextBoxで表示されています。
試しにXMLファイルを開いて適当な要素を改変したうえで更新ボタンを押下してください。表示される値が変更されるのを確認できるでしょう。
クライアント側実装 DataGridコントロールの表示カスタマイズ
さて、基本的なXMLファイルのバインディングは実現できました。しかし、自動で表を作成しているため、柔軟には運用できません。これだけではDataGridの機能を実感できないので、表示をカスタマイズしてみましょう。
DataGridで列の自動生成を行わず、各列を手動で挿入するようにします。手動挿入の場合、各列でバインドするプロパティは個別に設定できます。顧客リストのID、Name、Work、Ref要素はそのままテキスト表示し、Check要素はチェックボックスで表示しましょう。また、今回はテンプレートを使ったカスタマイズを試したいので、Mail要素はハイパーリンクを記述するためのHyperlinkButtonコントロールを使って表示させてみましょう。
Expression BlendでPage.xamlを編集し、DataGridを選択します。AutoGenerateColumns
プロパティをFalseに設定します。これで表の自動生成が行われなくなり、バインドされたデータが自動で表示されることがなくなります。
続いて、列を手動で挿入しましょう。DataGridに手動で列を挿入するにはDataGridColumn
クラスを使用します。Expression BlendのDataGridのプロパティで[Columns(コレクション)]項目で[...]をクリックします。
このダイアログからDataGridに手動で列を挿入することができます。
画面にもあるように[別のアイテムを追加]をクリックし[オブジェクトの選択]ウィンドウで[システムアセンブリの表示]にチェックを入れて追加できるアイテムを参照します。
列挙されている中の、DataGridTextColumnはテキスト表示する列を、DataGridCheckBoxColumnはチェックボックス表示する列を定義するためのクラスです。今回はテキスト表示するID、Name、Work、Ref各要素について、それぞれDataGridTextColumnを挿入します。チェックボックス表示するCheck要素については、DataGridCheckBoxColumnを挿入します。
また、前述の通りMail要素はハイパーリンクとして表示させるため、DataGridTemplateColumn
クラスを使います。このクラスにより、さまざまなコントロールをDataGrid上で扱うことが可能になります。ここでDataGridTemplateColumnを挿入しますが、実際にハイパーリンクを表示させる詳細については後述します。各アイテムは下に表示された上下ボタンを使って表示させる順番を変更することができます。
続いて各アイテムのプロパティを設定しましょう。HeaderはDataGridの先頭行に表示されるヘッダー文字列を指定するプロパティです。DataGridTextColumnにはフォントサイズを設定します。
ではここまでで変更を保存してVisual Studioに戻りましょう。
さらにVisual Studio側のXAMLペインでDataGridに変更を加えていきます。
<data:DataGrid Margin="48,48,248,192" Grid.Row="1" x:Name="list1" AutoGenerateColumns="False" IsReadOnly="False" IsEnabled="True" Grid.ColumnSpan="1" Grid.Column="0" CanUserResizeColumns="True" CanUserReorderColumns="False" > <data:DataGrid.Columns> <data:DataGridCheckBoxColumn Header="チェック" Binding="{Binding Path=Check}" /> <data:DataGridTextColumn Header="ID" Binding="{Binding Path=ID}" FontSize="16" IsReadOnly="True" CanUserReorder="False" CanUserResize="True" /> <data:DataGridTextColumn Header="名前" Binding="{Binding Path=Name}" FontSize="16" CanUserResize="False" IsReadOnly="True" /> <data:DataGridTextColumn Header="所属企業" Binding="{Binding Path=Work}" FontSize="16" IsReadOnly="True" /> <data:DataGridTemplateColumn Header="メール"> <data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<HyperlinkButton VerticalAlignment="Center" Content="メール送信" NavigateUri="{Binding Mail}" TargetName="_blank"></HyperlinkButton>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate> </data:DataGridTemplateColumn> <data:DataGridTextColumn Header="予定欄" Binding="{Binding Path=Ref}" IsReadOnly="True" FontSize="16" /> </data:DataGrid.Columns> </data:DataGrid>
先述したHyperlinkButtonの設置のため、DataGridTemplateColumnの下にDataGridTemplateColumn.CellTemplateおよびDataTemplateを配置します(太字部分)。DataTemplateの中で、実際に表示する内容を定義します。ここではメールアドレスへのハイパーリンクを表示するため、これらのタグにはさむようにツールボックスからHyperlinkButtonをドロップしました。なお、DataGridTemplateColumn以下には、DataGridを編集する際に表示するタグを指定するCellEditingTemplate
タグと、編集以外の時に表示するタグを指定するCellTemplate
タグを配置することができますが、今回はDataGridでの編集は行わないため、CellTemplate
タグを配置しています。
データバインドに関する記述ですが、今のところExpression Blendの[アイテムの追加]ではバインド内容の編集ができないようなので、太字で示しているようにVisual Studioで編集しました。既に親コントロール側でDataContextが設定されているため、プロパティ名を指定するだけでデータを取得できます。今回は下のような構文でバインドするプロパティを指定しました。
"{Binding Path=(バインドするプロパティ名)}"
DataGridTextColumnおよびDataGridCheckBoxColumnでは、Binding
プロパティに上記の構文で表示するプロパティを指定します。
DataGridTemplateColumn内に配置したHyperlinkButtonコントロールでは、リンク先URIを表すNavigateUri
プロパティに"{Binding Mail}"を指定して、メールアドレスへのリンクを生成しています。バインドするプロパティ名を記述する際には、「Path=」の部分を省略することもできます。ここでは実際にこの記法を用いました。
では実行しましょう。
冒頭の成功時の画面で示した通りに出力されているでしょうか。自動生成した表と大きな違いはありませんが、子要素を個別にバインドしているためそれぞれ自由にスタイルを変更することができます。メールアドレスはHyperlinkButtonで表示しているのでクリックするとメーラが起動し顧客リストの上のメールアドレス宛にメールが作成できます。
今回のサンプルではDataTemplateの中でHyperlinkButtonを使用しましたが、この方法であらゆるコントロールを配置できます。例えばImageコントロールを使って、オブジェクトからURIを取得して、DataGrid中に画像を表示することも可能です。このように、DataTemplateを利用すればDataGridコントロールの活用の幅は大きく広がります。
.NET Framework 3.5 SP1のWPFのデータバインディングでは、例えばDateTime型の変数をバインド元にする場合、次のような記述で書式指定することができます。
<TextBox Text="{Binding Path=date, StringFormat=yyyy年MM月dd日}"/>
しかし、残念ながら現在のところSilverlight 2はこの機能に対応していません。必要な場合はビハインドコードで
string printDate = date.ToString("yyyy年MM月dd日");
と記述するなど、値を整形した後にバインドする必要があります。
サンプル2:各種コントロールへのバインドとデータの書き換え
サンプル1に機能を追加し、DataGrid以外のコントロールへのデータバインディングとWebサービスへのデータ送信機能を実装してみましょう。
実際の動作ですが、
- ドロップダウンで顧客を選択して、詳細画面にデータを読み込む
- 顧客リストの予定欄(=Ref要素)をクライアントで編集し、更新内容をサーバに送信する
という2つの機能を実装することにします。
以下に実行時の画面を示します。
サンプル2の構成
以下に簡単な構成図を示します(初回のDataGridのデータ読込はサンプル1と同じですので省略します)。
サンプル2で追加するファイルはサーバ側のUpdateCustomerRef.aspxとそのビハインドコードだけです。このASP.NETサイトはXMLファイルの書き換えを実行します。これ以外は既存のファイルに追記します。
顧客データ更新Webサービスの作成
では、早速実装に入りましょう。サンプル1と同様サーバサイドから実装します。ソリューションエクスプローラからサーバ側にUpdateCustomerRef.aspxの名前で「Webフォーム」を追加します。以下がUpdateCustomerRef.aspx.csのソースコードです。
using System.IO; using System.Text; namespace DataBindApplication1.Web { public partial class UpdateCustomerRef : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { string id = Request.QueryString["data1"]; string re = Request.QueryString["data2"]; var xml = XDocument.Load(Server.MapPath("~/App_Data/Customer.xml")); var Ref = from item in xml.Descendants("Item") where (string)item.Element("ID")== id select item; foreach (var item in Ref) { item.SetElementValue("Ref", re); } xml.Save(Server.MapPath("~/App_Data/Customer.xml")); } } }
このサービスは次のようなURIで呼び出され、クエリストリングでdata1とdata2というパラメータを受け取ります。
http://localhost:8080/UpdateCustomerRef.aspx?data1={0}&data2={1}
クライアント側は後で実装しますが、クライアント側から更新のリクエストが送られる際、data1に更新するCustomer
オブジェクトのID要素、data2にRef要素の新しい内容が渡されます。
ここでは、LINQでXMLから該当データを取得し、クライアントから受け取ったRef要素で更新して保存しています。サーバサイドの詳細な動作は今回のサンプルの趣旨から外れますので詳しい説明は省略いたします。
クライアント側の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サービスについて扱っていきます。ご期待ください。