CodeZine(コードジン)

特集ページ一覧

Silverlight 2でのデータバインディング

Silverlight 2で作成する業務アプリケーション入門(5)

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2009/01/29 14:00

 .NET Framework 3.5のWPFは強力なデータバインディングをサポートしていますが、そのサブセット的位置づけにあるSilverlight 2もやはり多彩なデータバインディングに対応しています。特に、業務アプリケーションの多くは、一般に帳票画面と呼ばれるリソースを表示・編集する画面の集合でできており、この実装においてデータバインディングは欠かせない技術と言えます。本稿ではSilverlight 2のデータバインディングの概要と業務アプリケーションへの実装について扱います。

データバインディングの概要

 データバインディングとは、ユーザーインターフェイスのプロパティと他のコントロールやクラスのプロパティを関連づける処理のことです。データバインディングは主に、データベースやWebサービスなどから取得したリソースを、クライアント上のコントロールに表示する場合などに利用します。

 データバインディングを使わない場合は、「リソースを順に取得しながら、対応するコントロールのプロパティに代入する」といったコードが必要になります。例えば、次のようなコードになります。

データバインディングがない場合のソースコードの例
var resource = getResource(); //データベースやWebサービスなどからリソースを取得
nameTextBox.Text = resource.Name;         //テキストボックスに値を代入
addressTextBlock.Text = resource.Address; //テキストブロックに値を代入

 一方、データバインディングを使えば、「このコントロールには○○というフィールドを表示させる」という設定をしておくだけで、実際の値の割り当て(=バインディング)は自動的に行われます。詳細は後で扱いますが、データバインディングがどれぐらい簡潔な記述でできるかを少し見てみましょう。XAMLコード側では次のようにコントロールと、割り当てるプロパティを関連づけます。

データバインディングを使った場合のソースコードの例(XAMLコード)
<TextBox Text="{Binding Path=Name}"  x:Name="nameTextBox"/>
<TextBlock Text="{Binding Path=Address}"  x:Name="addressTextBlock"/>

 合わせて分離コードで、バインドするオブジェクトを指定します。

データバインディングを使った場合のソースコードの例(C#分離コード)
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つのモードがあります。

Silverlightのデータバインドモード
モード 概要
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プロパティにバインディングで値を設定する場合は下のように記述します。

XAMLファイル内における{Binding…}構文の例
<TextBlock Text="{Binding Path=FirstName Mode=OneWay}" />

 この場合、Textプロパティに、バインド元オブジェクトのFirstNameプロパティの値がOneWayモードでバインディングされます。

 さて、今の構文においては、プロパティの種類のみが指定され、バインド元オブジェクトが明示されていないことに気づかれたかと思います。Silverlightのデータバインドは、コントロールごとに特定のバインド元オブジェクトを指定することもできますが、特に指定しない場合は親コントロールのDataContextプロパティで指定されたオブジェクトが継承されます。下にDataContextプロパティの設定例を示します。

分離コードにおけるDataContext設定の例(thisはUserControl等、親コントロールを指す)
object1.FirstName = "Hello,World!";
this.DataContext = object1;

 この記述の場合、バインド元オブジェトとしてobject1が指定されています。従って、先ほどのTextBlockにバインドされるのはobject1のFirstNameプロパティ、つまり、"Hello,World!"という文字列になります。もちろんこの例ではobject1はFirstNameプロパティを持つクラスのインスタンスでなければなりません。

 前述の通り、ユーザーコントロールのDataContextプロパティはそのユーザーコントロール以下に配置されたコントロールに継承されます。例えばGridやStackPanelといったレイアウト系のコントロールにDataContextを設定すると、それら以下のコントロールにも同様に設定したDataContextが適用されます。

 一方、データソースを指定してバインドする場合は次のように記述します。

バインド先のXAMLコードの例
<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を実行・成功画面
サンプル1を実行・成功画面

サンプル1の構成

 サーバからXML形式のデータを非同期的に取得し、LINQ to XMLを使用してデータを加工したうえでDataGridにバインドします。

 以下に簡単な構成図を示します。

図:サンプル1の動作モデル
図:サンプル1の動作モデル

 以下に作成するファイルを示します。

サンプル1で作成するファイル
サーバ/クライアント ファイル名 概要
クライアント側(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件のデータが入っています。

Customer.xml
<?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」を右クリックし[開く]を選択しましょう。以下に追記後のコードを示します。

Defaut.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の参照も追加しておきます。

 編集時の画面は次のとおりです。

Expression BlendでPage.xamlを編集
Expression BlendでPage.xamlを編集

 上段グリッドにTextBlockを配置し下段グリッドにはアセットライブラリからDataGridとButtonをドロップします。それぞれマウスで適当な位置に配置し、大きさなどのスタイルを整えます。

アセットライブラリからDataGridを追加
アセットライブラリからDataGridを追加

 DataGridに設定するプロパティですが、AutoGenerateColumnsプロパティにTrueを設定しました。これはバインドするデータによって自動的に列を生成する機能です。まずはこの機能を利用してDataGridの基本的な動作を試してみましょう。

 バインドに関する設定を行うには、プロパティウィンドウからItemSourceのボックスをクリックします。以下の画面が出現します。

ItemsSourceを設定
ItemsSourceを設定

 メニューから[カスタム式]を選択します。ここに「{Binding}」とだけ入力します。今回、データソースを指定するDataContextプロパティはビハインドコードで設定しますのでPage.xaml側に記述するのはこれだけで十分です。

 さらにUserControlには読み込み時に発生するLoadedイベントのハンドラを設定しました。

 以下に設定した主なプロパティの一覧を示します。

主なプロパティ(レイアウト関連のプロパティなどは省略)
コントロール名 プロパティ 概要
UserControl Loadedイベント ユーザーコントロール読み込み時に発生するイベントのハンドラ。WebサービスからXML形式の顧客リストを読み込む機能を実装する
DataGrid AutoGenerateColumns 読み込んだデータに応じて列を自動的に生成するプロパティ
ItemsSource バインド情報を設定
IsReadOnly 読み取り専用の属性を設定
Button Clickイベント ページ更新のハンドラ

 実際のXAMLコードは次のとおりです。

Page.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を作成します。以下にコードを示します。

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を作成します。

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回も併せて参照してください。

  他の各部の動作についてはコメントを参照いただくとして、ここではバインディングに関連する部分に注目してみます。

DataContextの設定
this.DataContext = customers;

 この記述でユーザーコントロール全体のバインド元オブジェクトとして、customers変数が設定されます。この結果XAMLファイル内で特にソースを明示せずデータバインディングを実行した場合、ここで設定したDataContextが継承されます。

 では、ここまででとりあえず実行してみましょう。

列が自動生成されたDataGrid
列が自動生成されたDataGrid

 サーバ上の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(コレクション)]項目で[...]をクリックします。

Columns(コレクション)項目で[...]をクリック
Columns(コレクション)項目で[...]をクリック

 このダイアログからDataGridに手動で列を挿入することができます。

別のアイテムを追加をクリック
別のアイテムを追加をクリック
[システムアセンブリ]の表示にチェックをしてオブジェクトを選択
[システムアセンブリ]の表示にチェックをしてオブジェクトを選択

 画面にもあるように[別のアイテムを追加]をクリックし[オブジェクトの選択]ウィンドウで[システムアセンブリの表示]にチェックを入れて追加できるアイテムを参照します。

 列挙されている中の、DataGridTextColumnはテキスト表示する列を、DataGridCheckBoxColumnはチェックボックス表示する列を定義するためのクラスです。今回はテキスト表示するID、Name、Work、Ref各要素について、それぞれDataGridTextColumnを挿入します。チェックボックス表示するCheck要素については、DataGridCheckBoxColumnを挿入します。

 また、前述の通りMail要素はハイパーリンクとして表示させるため、DataGridTemplateColumnクラスを使います。このクラスにより、さまざまなコントロールをDataGrid上で扱うことが可能になります。ここでDataGridTemplateColumnを挿入しますが、実際にハイパーリンクを表示させる詳細については後述します。各アイテムは下に表示された上下ボタンを使って表示させる順番を変更することができます。

 続いて各アイテムのプロパティを設定しましょう。HeaderはDataGridの先頭行に表示されるヘッダー文字列を指定するプロパティです。DataGridTextColumnにはフォントサイズを設定します。

DataGridTextColumnのHeaderプロパティに"ID"と設定しているところ
DataGridTextColumnの^^Header^^プロパティに"ID"と設定しているところ

 ではここまでで変更を保存してVisual Studioに戻りましょう。

 さらにVisual Studio側のXAMLペインでDataGridに変更を加えていきます。

Page.xamlの内容 太字部分はVisual Studioで記述
<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型の変数をバインド元にする場合、次のような記述で書式指定することができます。

WPFにおける値整形の例 dateプロパティは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実行画面
サンプル2実行画面

サンプル2の構成

 以下に簡単な構成図を示します(初回のDataGridのデータ読込はサンプル1と同じですので省略します)。

図:サンプル2の動作モデル(初回のDataGridのデータ読込以降の動作)
図:サンプル2の動作モデル(初回のDataGridのデータ読込以降の動作)

 サンプル2で追加するファイルはサーバ側のUpdateCustomerRef.aspxとそのビハインドコードだけです。このASP.NETサイトはXMLファイルの書き換えを実行します。これ以外は既存のファイルに追記します。

顧客データ更新Webサービスの作成

 では、早速実装に入りましょう。サンプル1と同様サーバサイドから実装します。ソリューションエクスプローラからサーバ側にUpdateCustomerRef.aspxの名前で「Webフォーム」を追加します。以下がUpdateCustomerRef.aspx.csのソースコードです。

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でコントロールを追加しましょう。以下に編集中の画面を示します。

Page.xamlを編集
Page.xamlを編集

 XAMLは次のようになります。

Page.xamlの内容(太字部分はバインディングに関する記述 {Binding…}構文はVisual Studioで追記)
<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に変更を加えます。以下に追記したコードを示します。

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で記述した、更新ボタンのイベントハンドラを次のように変更します。

Page.xaml.csの内容(データ更新部分)
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 or OneWay or TwoWay?

 今回のサンプルはデータバインディングのバインドモードとして、OneTimeモードのみを使用しています。

 OneWayモードを使えば、最後のデータ更新をサーバに投げた後、再読込することなく、元のDataGridの予定欄の表示を更新させることができます。また、TwoWayモードを使うと、テキストボックスを更新した地点で、書き換えた内容がオブジェクトに反映されるため、イベントハンドラで明示的にテキストボックスの内容をオブジェクトに書き戻す必要が無くなります。なお、OneWay / TwoWayモードを使うには、バインド元オブジェクトであるCustomerクラスにINotifyPropertyChangedインターフェイスを実装する必要があります。

 Silverlightのようなリッチクライアント環境では、データがローカルにあるのではなく、サーバ側に存在することも多いため、TwoWayモードよりもOneTime / OneWayモードでの実装が自然な場合も多いでしょう。

まとめ

 今回は、サーバとXMLデータをやり取りするというモデルを使ってデータバインディングを体験しました。Silverlighでオブジェクトとコントロールのプロパティの連係を簡単に実現できることが実感できたかと思います。最終回の次回はWebサービスについて扱っていきます。ご期待ください。

  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

  • WINGSプロジェクト 土井 毅(ドイ ツヨシ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂...

All contents copyright © 2005-2022 Shoeisha Co., Ltd. All rights reserved. ver.1.5