Part 3. 高度な Silverlight 2 アプリ開発

というわけで、Silverlight 2 & Expression Blend 2.5 の記事の最終回 Part 3. では、ここまでの知識を総括して、XML Web サービス経由でデータベースからデータを取り出して ListBox に表示するアプリケーションを開発してみることにします。

なお、以降の作業は Part 2. で完成させたソリューションファイルに対して行います。Expression Blend から Visual Studio の方に戻り、以下の作業を続けてください。

※ Part 2. 終了時点のファイルはこちらになります。

[Step 10] データ取得のための XML Web サービスの準備

まず、サーバ側にデータベースからデータを取得して送り返す XML Web サービスを準備します。出版社サンプルデータベースである pubs.mdf ファイルを準備し、以下の作業を行います。(準備できない方は、後ろに書いてある回避策を使ってください。)

  • Web サイトの App_Data フォルダ下に pubs.mdf ファイルを貼り付ける。
  • App_Code フォルダ下に、Pubs.dbml ファイル(LINQ to SQL の O/R マッピングファイル)を新規作成する。
  • Pubs.dbml ファイルを開き、サーバエクスプローラからすべてのテーブルをドラッグ&ドロップで追加する。(ドラッグ&ドロップできたらファイルはセーブしておきます。)

image

  • WebService.asmx ファイルを開き、LINQ to SQL でデータを取得して返すコードを記述します。

image

ここでは LINQ to SQL の詳細は解説しませんが、実装上のポイントは以下の通りです。

  • using キーワードにより、System.Linq と System.Data.Linq 名前空間の利用宣言を行います。(LINQ to SQL クエリを使うために必要)
  • データを返すための構造体として、AuthorList クラスを定義します。ここでは簡単のため、au_id (著者 ID)と au_name (著者名)の 2 つのフィールドを持つクラスを定義します。
  • AuthorList クラスの配列を返す GetData() メソッドを XML Web サービスとして定義します。LINQ to SQL のクエリを記述した後、.ToArray() メソッドを呼び出すことにより、LINQ クエリの実行結果を静的配列に変換することができます。

実装が終わった WebService.asmx ファイルは以下の通りです。完成したら、WebService.asmx ファイルをブラウザから呼び出して、動作確認を行ってください。

    1: <%@ WebService Language="C#" Class="WebService" %>
    2:  
    3: using System;
    4: using System.Web;
    5: using System.Web.Services;
    6: using System.Web.Services.Protocols;
    7:  
    8: using System.Linq;
    9: using System.Data.Linq;
   10:  
   11: [WebService(Namespace = "https://tempuri.org/")]
   12: [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   13: public class WebService  : System.Web.Services.WebService {
   14:  
   15:     [WebMethod]
   16:     public string GetMessage(string name)
   17:     {
   18:         return "Hello World, " + name;
   19:     }
   20:  
   21:     [WebMethod]
   22:     public AuthorList[] GetData()
   23:     {
   24:         using (PubsDataContext pubs = new PubsDataContext())
   25:         {
   26:             var authors = from a in pubs.authors
   27:                           where a.state == "CA"
   28:                           select new AuthorList
   29:                           {
   30:                               au_id = a.au_id,
   31:                               au_name = a.au_fname + " " + a.au_lname
   32:                           };
   33:             return authors.ToArray();
   34:         }
   35:     }        
   36: }
   37:  
   38: public class AuthorList
   39: {
   40:     public string au_id { get; set; }
   41:     public string au_name { get; set; }
   42: }

image

※ pubs.mdf データベースファイルを用意できない場合について

この場合には、ダミーデータを返す XML Web サービスを開発してください。具体的には、WebService.asmx ファイル上に以下のようなコードを実装します。

    1: [WebMethod]
    2: public AuthorList[] GetData()
    3: {
    4:     AuthorList a1 = new AuthorList { au_id = "172-32-1176", au_name = "Johnson White" };
    5:     AuthorList a2 = new AuthorList { au_id = "213-46-8915", au_name = "Marjorie Green" };
    6:     AuthorList a3 = new AuthorList { au_id = "238-95-7766", au_name = "Cheryl Carson" };
    7:     AuthorList a4 = new AuthorList { au_id = "267-41-2394", au_name = "Michael O'Leary" };
    8:     AuthorList a5 = new AuthorList { au_id = "274-80-9391", au_name = "Dean Straight" };
    9:     AuthorList a6 = new AuthorList { au_id = "409-56-7008", au_name = "Abraham Bennet" };
   10:     AuthorList a7 = new AuthorList { au_id = "427-17-2319", au_name = "Ann Dull" };
   11:     return new AuthorList[] { a1, a2, a3, a4, a5, a6, a7 };
   12: }        

[Step 11] XML Web サービスの呼び出しと ListBox へのデータバインド

引き続き、XML Web サービスの呼び出しと ListBox へのデータバインドを行います。まず、Silverlight 2 アプリケーション側のサービス参照を更新し、新しく追加した、GetData() メソッドの情報をプロキシクラスに取り込みます。

image

次に一覧表示ボタンのイベントハンドラとして、XML Web サービス呼び出しとデータバインドを実装します。

  • Page.xaml ファイルを開きます。
  • XAML コードエディタを開き、Button オブジェクトに Click イベントハンドラを追加します。
  • コードビハインドファイルを開き、イベントハンドラコードを追加します。

image

    1: private void button1_Click(object sender, RoutedEventArgs e)
    2: {
    3:     button1.IsEnabled = false;
    4:     ServiceReference1.WebServiceSoapClient proxy = new ServiceReference1.WebServiceSoapClient();
    5:     proxy.GetDataCompleted += new EventHandler<SilverlightApplication1.ServiceReference1.GetDataCompletedEventArgs>(proxy_GetDataCompleted);
    6:     proxy.GetDataAsync();
    7: }
    8:  
    9: void proxy_GetDataCompleted(object sender, SilverlightApplication1.ServiceReference1.GetDataCompletedEventArgs e)
   10: {
   11:     button1.IsEnabled = true;
   12:     listBox1.ItemsSource = e.Result;
   13: }

実装が終了したら、アプリケーションを実行してみます。ボタンを押下すると、XML Web サービスが呼び出され、データバインドが実行されます。実行結果は以下のようになります。

image

ListBox の各行に「SilverlightApplication1.ServiceReference1.AuthorList」という文字列が表示されていることに着目してください。前述のコードでは、ListBox の ItemsSource プロパティに AuthorList オブジェクトのコレクションを割り当てていますが、ListBox コントロールはこのオブジェクトの表示方法を知りません。このため、既定の状態では、下図に示すように、各項目について .ToString() メソッドを呼び出して ListBox に表示する、という挙動を行います。

image

AuthorList オブジェクトの中身(つまり著者 ID や著者名)を取り出して ListBox 内に表示させるためには、ListBox コントロールにレンダリングテンプレート(各行を描画する際に利用するテンプレート)を指定しなければなりません。

[Step 12] ListBox コントロールに対する ItemTemplate の指定

ListBox コントロールに対するレンダリングテンプレートの指定には、Expression Blend を利用します。Expression Blend を使い、以下の作業を行います。

  • Page.xaml ファイルを開きます。
  • 「オブジェクトとタイムライン」ウィンドウの listBox1 を右クリックして、コンテキストメニューを開きます。
  • 「他のテンプレートの編集」 →「ItemTemplate の編集」→「空アイテムの作成」を選択します。
  • DataTemplate リソースの作成ウィンドウが開くので、OK ボタンを押してデータテンプレートの編集画面を開きます。

image

image

上記作業を行う際に、 「コントロールパーツ(テンプレート)の編集」ではなく「他のテンプレートの編集→ItemTemplate の編集」の方を選択することに注意してください。この二つは全く違う機能です。

  • 「コントロールパーツ(テンプレート)の編集」とは、ListBox コントロールそのものの描画方法(外観)を変えるための機能です。
  • 「ItemTemplate の編集」とは、ListBox 内の各行のレンダリング方法を変えるための機能です。

ItemTemplate の編集モードに入ると、デザイナが listBox1→DataTemplate の編集モードに入ります。

※ (参考) ItemTemplate の編集に入っているのに DataTemplate と表示されるのは、「ListBox コントロールの ItemTemplate プロパティに、データ表示のためのレンダリングテンプレートである DataTemplate オブジェクトを割り当てる」ためです。つまり、"ItemTemplate" は ListBox のプロパティ名、"DataTemplate" はそこに割り当てるオブジェクトのクラス名です。このことは、最後に生成される XAML コードを見るとよくわかるので、興味がある方は見てみてください。

image

このテンプレートの編集画面では、AuthorList オブジェクトを、どのように 1 行としてレンダリングするのかを設定します。著者 ID と著者名を表示したい場合には、下図のようなテンプレートを設定することになります。

image

具体的には、以下の作業を行います。

  • 既定で貼り付いている Grid レイアウトコントロールを、StackPanel レイアウトコントロールに差し替えます。(レイアウトの種類の変更 → StackPanel)
  • StackPanel コントロールの下に、TextBlock コントロールを二つ貼り付けます。(ツールボックスからダブルクリックで追加するとラク。間違って TextBox を貼り付けやすいので注意してください。自分は結構ミスします...)

image

image

  • StackPanel レイアウトコントロールの Orientation プロパティを、Vertical (縦に並べる)から Horizontal (横に並べる)に変更します。
  • 各 TextBlock の Margin プロパティを 4 に変更します。(既定だと 0 のため、文字がくっついて表示されてしまう。これを避けるため。なお、β2 版では IME 制御が不完全なためか、たまに Margin 値を打ち込むときに IME が全角になってしまい、うまく入力できないことがありますのでご注意を。)

image

最後に、各 TextBlock に対して、データバインドの指定を行います。

  • 左側の TextBlock を選択し、プロパティウィンドウの Text プロパティの右側にある「・」ボタン(詳細プロパティオプション)をクリックし、データバインドを選択します。
  • 「データバインドの作成」ウィンドウの「明示的なデータコンテキスト」タブを開き、カスタムパスとして "au_id" を設定します。(※ データフィールドタブのカスタムパスの指定は、コンテキストバインドの機能ではないため、ここでは使いません。)
  • 同様に、右側の TextBlock に対しては、カスタムパスとして "au_name" を設定します。

image

image

image

以上の作業により、レンダリングのためのテンプレートが設定できます。(見た目では、TextBlock の表示がつぶれます。)

image

ファイルをセーブしたら、Visual Studio に戻り、アプリケーションを実行してみてください(ブラウザを再起動することを忘れずに^^)。正しくアプリケーションが作成できていれば、以下のような一覧表示が行えるはずです。

image

最終的に完成した XAML コードとコードビハインドを以下に示します。

[Page.xaml ファイル]

    1: <UserControl
    2:     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3:     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    4:     xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    5:     xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    6:     mc:Ignorable="d"
    7:     x:Class="SilverlightApplication1.Page"
    8:     d:DesignWidth="400" d:DesignHeight="300">
    9:     <UserControl.Resources>
   10:         <DataTemplate x:Key="DataTemplate1">
   11:             <StackPanel Orientation="Horizontal" Margin="0,0,0,0">
   12:                 <TextBlock Text="{Binding Mode=OneWay, Path=au_id}" TextWrapping="Wrap" Margin="4,4,4,4"/>
   13:                 <TextBlock Text="{Binding Mode=OneWay, Path=au_name}" TextWrapping="Wrap" Margin="4,4,4,4"/>
   14:             </StackPanel>
   15:         </DataTemplate>
   16:     </UserControl.Resources>
   17:  
   18:     <Grid x:Name="LayoutRoot" Background="White" >
   19:         <Grid.RowDefinitions>
   20:             <RowDefinition Height="Auto"/>
   21:             <RowDefinition Height="*"/>
   22:         </Grid.RowDefinitions>
   23:         <Grid.ColumnDefinitions>
   24:             <ColumnDefinition Width="Auto"/>
   25:             <ColumnDefinition Width="*"/>
   26:             <ColumnDefinition Width="Auto"/>
   27:         </Grid.ColumnDefinitions>
   28:         <TextBlock HorizontalAlignment="Left" VerticalAlignment="Stretch" Text="著者一覧表示" TextWrapping="Wrap" Margin="8,8,8,8" Width="Auto" x:Name="textBlock1"/>
   29:         <Button Margin="8,8,8,8" Content="一覧表示" Grid.Column="2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" x:Name="button1" Click="button1_Click" />
   30:         <ListBox Margin="8,8,8,8" Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.ColumnSpan="3" x:Name="listBox1" ItemTemplate="{StaticResource DataTemplate1}"/>
   31:     </Grid>
   32: </UserControl>

[Page.xaml.cs ファイル]

    1: using System;
    2: using System.Windows;
    3: using System.Windows.Controls;
    4: using System.Windows.Documents;
    5: using System.Windows.Ink;
    6: using System.Windows.Input;
    7: using System.Windows.Media;
    8: using System.Windows.Media.Animation;
    9: using System.Windows.Shapes;
   10:  
   11: namespace SilverlightApplication1
   12: {
   13:     public partial class Page : UserControl
   14:     {
   15:         public Page()
   16:         {
   17:             // Required to initialize variables
   18:             InitializeComponent();
   19:         }
   20:  
   21:         private void button1_Click(object sender, RoutedEventArgs e)
   22:         {
   23:             button1.IsEnabled = false;
   24:             ServiceReference1.WebServiceSoapClient proxy = new ServiceReference1.WebServiceSoapClient();
   25:             proxy.GetDataCompleted += new EventHandler<SilverlightApplication1.ServiceReference1.GetDataCompletedEventArgs>(proxy_GetDataCompleted);
   26:             proxy.GetDataAsync();
   27:         }
   28:  
   29:         void proxy_GetDataCompleted(object sender, SilverlightApplication1.ServiceReference1.GetDataCompletedEventArgs e)
   30:         {
   31:             button1.IsEnabled = true;
   32:             listBox1.ItemsSource = e.Result;
   33:         }
   34:     }
   35: }

以上で基本的には終わりですが、オマケでちょっと遊びをしてみたいと思います。:-)

[Step 13] レンダリング変形機能の活用

従来の Windows フォームや VB6 などのアプリケーションと異なり、Silverlight 2 では、ベクターグラフィクスベースのレンダリングエンジンを利用した描画を行っています。

  • これは、「TextBox や ListBox などがドット絵として描画されるのではなく、ベクターグラフィクスとして描画される」というものです。
  • このような特性を持つが故に、Expression Blend や Visual Studio では、デザイナ画面で「画面そのものを拡大表示する」ことができるようになっています。

image

例えば、Expression Blend 上で、以下のような作業をしてみてください。

  • ListBox コントロールの四隅をつまんで回転させてみる。
  • Button コントロールを台形変形させてみる。(プロパティウィンドウ内の「変換」タブを開き、その中にある「傾斜」をいじってみてください。)

image

このように変形しても、アプリケーションは全く問題なく動作します。(ボタンも問題なく押せるし、リストボックスも問題なく斜めにスクロールします。)

image

実際の業務アプリケーションでこのような変形加工を行うことはないと思いますが^^、Silverlight 2 がベクターベースの描画エンジンを利用している、というポイントは非常に興味深いポイントであるため、覚えておいていただければと思います。

[Step 14] DataGrid コントロールの利用

ちなみに、ここまで ListBox コントロールを使ってきましたが、DataGrid コントロールを使うと簡単にグリッドにデータ表示することができます。これに関しては、細かいやり方は説明しませんので、ここまでの解説を参考にして実際にいじってみてください。:-) キーポイントは以下の通りです。

  • 既定では、Expression Blend のツールボックス上に DataGrid コントロールは現われません。これは、DataGrid コントロールが拡張コントロールであり、参照設定が必要なためです。プロジェクト→参照設定の追加から、C:\Program Files (x86)\Microsoft SDKs\Silverlight\v2.0\Libraries\Client 内にあるSystem.Windows.Controls.Data.dll ファイルに参照設定を張り、さらにアセットライブラリのカスタムコントロール内から選択することで、DataGrid が使えるようになります。
  • データバインドのやり方は基本的に同じで、ItemsSource プロパティにオブジェクトコレクションを割り当てます。

image

※ レンダリングテンプレートの変更は、Columns プロパティに DataGridCellTemplate を割り当てることによって行う....のですが、現時点では Expression Blend からは設定できないかもしれません。(探してみたけど見当たらず....) XAML コードからであれば指定できるので、興味がある方はやってみてください。具体的なやり方は以下のページが参考になると思います。https://blogs.msdn.com/scmorris/

※ 完成したソリューションはこちらになります。

[本エントリと全体のまとめ]

というわけで、本エントリのキーポイントをまとめると以下のようになります。

  • ListBox コントロールの ItemsSource プロパティにオブジェクトコレクションを割り当てると、データバインドができる。
  • データバインド時のレンダリングテンプレートは、ItemTemplate で指定する。
  • Silverlight 2 はベクターベースのレンダリングエンジンを利用している。

3 つのエントリにわけて Silverlight 2 アプリケーションの基本的な開発方法を解説してきましたが、Silverlight 2 と Expression Blend はとにかく面白いツールだと思います。特に、リッチな Web アプリケーションの UI 画面を C# で開発できるというメリットは極めて大きく、また新しい UI 設計概念も非常によくできている、というのが個人的な感想です。

ぜひ、本エントリなどを活用して Silverlight 2 と Expression Blend による開発を楽しんでください。

……というわけでやっと書き終わりました;。やっぱり結構大変っすねー;。

次は何を書こうかな……