Silverlightで作る付箋紙アプリ その3

今回はサーバーサイドの処理を実装していく。

まず、付箋紙が保持する情報を決める。

  • 付箋紙Id(システムのための識別子)
  • コメント
  • 左端、上端の座標
  • 横幅、縦高さ
  • 付箋紙の色

この情報をサーバー側でXMLとして保持する。XMLのフォーマットは以下のようにして、「App_Data」フォルダ配下に「items.xml」というファイル名で保存しておく。

App_Data/items.xml
<?xml version="1.0" encoding="utf-8" ?>
<items>
   <item>
       <id>20080404-1010221234</id>
       <left>10</left>
       <top>20</top>
       <width>200</width>
       <height>150</height>
       <color>Blue</color>
       <comment>Hello, World</comment>
   </item>
   <item>
       <id>20080405-1111221234</id>
       <left>30</left>
       <top>40</top>
       <width>220</width>
       <height>170</height>
       <color>White</color>
       <comment>Welcome!!</comment>
   </item>
</items>

適当にデータを入れておいた。

付箋紙の一覧を取得する

付箋紙の一覧のXMLを取得するURLは「~/Item/List.aspx」にする。ちなみに、ASP.NET MVCを使うのに「.aspx」というパスを使う理由は、IISで拡張子なしのパスへのマッピング方法がわからないから。

クライアント側はこのURLに対してリクエストを行い、付箋紙一覧のXMLデータを取得/解析し、その情報に基づいて付箋紙コントロールを配置することになる。

まずはこのURLに応答するためにURLルーティングの設定を変える必要があるので、「Global.asax.cs」を以下のように変更する。

Global.asax.cs

using System;
using System.Web.Mvc;
using System.Web.Routing;

namespace Fusen.Web {
   public class GlobalApplication : System.Web.HttpApplication {
       public static void RegisterRoutes(RouteCollection routes) {
           routes.Add(new Route("Default.aspx", new MvcRouteHandler()) {
               Defaults = new RouteValueDictionary(new { controller = "Home", action = "Index" }),
           });
           routes.Add(new Route("{controller}/{action}.aspx", new MvcRouteHandler()));
       }

       protected void Application_Start(object sender, EventArgs e) {
           RegisterRoutes(RouteTable.Routes);
       }
   }
}

「{controller}/{action}.aspx」というルーティングルールを追加している。こうすることで「Item/List.aspx」というURLが要求されるとItemControllerコントローラクラスのListアクションメソッドが呼び出されることになる。

では、ItemControllerクラスを実装していく。
「Controllers」フォルダに「ItemController.cs」というファイル名でC#ソースファイルを新規作成して、以下のように実装する。

Controllers/ItemController.cs
using System;
using System.IO;
using System.Web.Mvc;
using System.Text;
using System.Diagnostics;

namespace Fusen.Web.Controllers {
   public class ItemController : Controller {
       public ItemController() {
       }

       public void List() {
           var fileName = Request.MapPath("~/App_Data/items.xml");

           using(var sr = new StreamReader(fileName, Encoding.UTF8)) {
               Response.ContentEncoding = Encoding.UTF8;
               Response.ContentType = "text/xml";
               Response.Output.Write(sr.ReadToEnd());
               Response.End();
           }
       }
   }
}

これでビルドして「http://localhost:1100/Item/List.aspx」にブラウザでアクセスするとXMLが出力されるはず。

次はSilverlight側の処理。

まずは付箋紙の情報を格納するクラスを作る。ファイル名は「Item.cs」とする。

Item.cs

using System;
using System.Windows;
using System.Windows.Media;
using System.Diagnostics;

namespace Fusen {
    /// <summary>
    /// 付箋紙の情報を格納するクラス
    /// </summary>
    [DebuggerStepThrough]
    public class Item {
        /// <summary>
        /// 付箋紙のIdを取得、設定します。
        /// </summary>
        public string Id {
            get;
            set;
        }
        /// <summary>
        /// 左端の位置を取得、設定します。
        /// </summary>
        public double Left {
            get;
            set;
        }
        /// <summary>
        /// 上端の位置を取得、設定します。
        /// </summary>
        public double Top {
            get;
            set;
        }
        /// <summary>
        /// 横幅を取得、設定します。
        /// </summary>
        public double Width {
            get;
            set;
        }
        /// <summary>
        /// 縦高さを取得、設定します。
        /// </summary>
        public double Height {
            get;
            set;
        }
        /// <summary>
        /// コメントを取得、設定します。
        /// </summary>
        public string Comment {
            get;
            set;
        }
        /// <summary>
        /// 付箋紙の色の名前を取得、設定します。
        /// </summary>
        public string Color {
            get;
            set;
        }
        /// <summary>
        /// 付箋紙の色からブラシを取得します。
        /// </summary>
        public Brush Brush {
            get {
                return new LinearGradientBrush {
                    GradientStops = new GradientStopCollection {
                        new GradientStop { Color=FromName(Color), Offset=0 },
                        new GradientStop { Color=Colors.White, Offset=1 }
                    }
                };
            }
        }

        public Item() {
        }
        
        /// <summary>
        /// 指定した名前の色を取得します。
        /// </summary>
        /// <param name="name">色名</param>
        /// <returns>Color構造体</returns>
        private static Color FromName(string name) {
            return (Color)typeof(Colors).InvokeMember(
                name, System.Reflection.BindingFlags.GetProperty, null, null, null
            );
        }
    }
}

付箋紙のId、座標、サイズ、コメント、色*1、あと色名からブラシを生成するプロパティも用意する。

そして、付箋紙の情報を読み込む処理はページがロードされた時に行うので、「Page.xaml」を以下のように変更する。

Page.xaml
<UserControl x:Class="Fusen.Page"
   xmlns="http://schemas.microsoft.com/client/2007"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Loaded="UserControl_Loaded">
    
   <Canvas x:Name="LayoutRoot" Background="White"
           MouseLeftButtonDown="LayoutRoot_MouseLeftButtonDown">
   </Canvas>
    
</UserControl>

UserControlのLoadedイベントにイベントハンドラを追加している。

その処理が以下。

Page.xaml.cs

using System;
using System.Net;
using System.Xml.Linq;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Controls;

using Fusen.Controls;

namespace Fusen {
    public partial class Page : UserControl {
        /// <summary>
        /// 前回クリックされた時間
        /// </summary>
        private DateTime lastClickTime = DateTime.MinValue;

        public Page() {
            InitializeComponent();
        }

        private void LoadItems() {
            var webClient = new WebClient();
            webClient.DownloadStringCompleted += (s, e) => {
                var xml = XDocument.Parse(e.Result);
                var result = from item in xml.Descendants("item")
                             select new Item {
                                 Id = (string)item.Element("id"),
                                 Left = (double)item.Element("left"),
                                 Top = (double)item.Element("top"),
                                 Width = (double)item.Element("width"),
                                 Height = (double)item.Element("height"),
                                 Comment = (string)item.Element("comment"),
                                 Color = (string)item.Element("color")
                             };

                foreach(var item in result) {
                    var itemCtrl = new ItemControl {
                        DataContext = item
                    };
                    LayoutRoot.Children.Add(itemCtrl);
                }
            };
            webClient.DownloadStringAsync(new Uri("http://localhost:1100/Item/List.aspx"));
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e) { LoadItems(); }

        private void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
            var now = DateTime.Now;

            if((now - lastClickTime).TotalMilliseconds <= 200) {
                var curPos = e.GetPosition(this);

                var itemCtrl = new ItemControl();
                Canvas.SetLeft(itemCtrl, curPos.X);
                Canvas.SetTop(itemCtrl, curPos.Y);

                LayoutRoot.Children.Add(itemCtrl);
            }
            lastClickTime = now;
        }
    }
}

LoadItemsメソッドで「~/Item/List.aspx」にリクエストを行い、その結果をパースしてItemオブジェクトを生成する。

foreach(var item in result) {
    var itemCtrl = new ItemControl {
        DataContext = item
    };
    LayoutRoot.Children.Add(itemCtrl);
}

ItemオブジェクトのリストからItemControlを生成して、ルート要素の子コントロールとして追加している。この時ItemControlのDataContextプロパティにItemオブジェクトを割り当てている。

DataContextプロパティにオブジェクトを割り当てる事で、コントロールへのデータバインドが可能になるので、ItemControlを初期化するための煩雑なコードを書かなくてすむ。

Controls/ItemControl.xaml

<UserControl x:Class="Fusen.Controls.ItemControl"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="{Binding Width, Mode=TwoWay}" Height="{Binding Height, Mode=TwoWay}"
    Canvas.Left="{Binding Left, Mode=TwoWay}" Canvas.Top="{Binding Top, Mode=TwoWay}"
    Loaded="UserControl_Loaded" SizeChanged="UserControl_SizeChanged">
    
    <Grid>
        <Polygon x:Name="resizeArea" Fill="{Binding Brush}" Opacity="0.6" Stroke="Black" Points="0,0 0,150, 175,150, 200, 125, 200,0" />
        <Polygon x:Name="resizeGrip" Fill="White" Stroke="Silver" Points="175,150, 175,125, 200,125" Cursor="Hand"
                 MouseLeftButtonDown="resizeGrip_MouseLeftButtonDown" MouseLeftButtonUp="resizeGrip_MouseLeftButtonUp" />

        <Border x:Name="dragGrip" BorderBrush="Black" Width="20" Height="20" CornerRadius="10" Cursor="Hand"
                HorizontalAlignment="Left" VerticalAlignment="Top" Margin="6,6,0,0"
                MouseLeftButtonDown="dragGrip_MouseLeftButtonDown" MouseLeftButtonUp="dragGrip_MouseLeftButtonUp">
            <Border.Background>
                <RadialGradientBrush>
                    <GradientStop Color="White" Offset="0" />
                    <GradientStop Color="Silver" Offset="1" />
                </RadialGradientBrush>
            </Border.Background>
        </Border>
        
        <TextBlock x:Name="commentText" Text="{Binding Comment}" TextWrapping="Wrap"
                   FontSize="12" FontFamily="MS UI Gothic" Margin="5,35,5,30" />
        <TextBox x:Name="commentInput" Text="{Binding Comment, Mode=TwoWay}" AcceptsReturn="True" Visibility="Collapsed"
                 FontSize="12" FontFamily="MS UI Gothic" Margin="5,35,5,30" />
    </Grid>
    
</UserControl>

「{Binding}」構文を使ってDataContextプロパティに割り当てられたオブジェクトのプロパティを各コントロールのプロパティに割り当てている。

Modeを「TwoWay」にしているので、コントロールのプロパティ値が変更されれば、もちろんDataContextに割り当てられたオブジェクトのプロパティ値も変更される。

あとコメントを表示するTextBlockとその内容を変更するためのTextBoxも追加しておいた。TextBoxはデフォルトでは表示されていない。

これをビルドして実行すると、以下のようにXMLデータの情報に従った形で付箋紙が表示されるはず。

次回は、付箋紙の追加・編集・削除のサーバーサイド側の処理について

ここまでのソース

OneDrive

*1:2008/4/10 - 型をColorからstring型に変更した