Silverlight 2でTodoアプリを作ってみる その3

前回でTodoアイテムの追加画面を表示するところまでを作ったので、今回は追加画面の実際の処理を作る。

まずサーバーサイドの処理から実装する。「AddItem.aspx」というASP.NETページを新規作成して、コードビハインドファイルに以下の処理を追加する。

AddItem.aspx.cs

using System;
using System.Xml.Linq;

namespace SLTodo_Web {
    public partial class AddItem : System.Web.UI.Page {
        protected void Page_Load(object sender, EventArgs e) {
            string title = Request.QueryString["t"];
            string description = Request.QueryString["desc"];

            if(string.IsNullOrEmpty(title)) {
                Response.Output.Write(-1);
                Response.End();

            } else {
                string path = Server.MapPath("~/App_Data/items.xml");

                var xml = XDocument.Load(path);
                var item = new XElement("Item",
                    new XElement("Title", title),
                    new XElement("Description", description),
                    new XElement("Complete", false)
                );
                xml.Root.Add(item);
                xml.Save(path);

                Response.Output.Write(item.ToString());
                Response.End();
            }
        }
    }
}

クエリからタイトルと詳細情報を取得して、その情報を「App_Data/items.xml」に追加する。想定しているURLのフォーマットは「http://localhost:1030/AddItem.aspx?t=Hoge&desc=HogeHoge」というかんじ。

レスポンスとしては、クエリが足りなければ-1を返す。追加に成功すれば追加したXMLの要素を返す(0でもよかった)。
次は追加画面でAddボタンが押された時の処理を追加する。

AddItemView.xaml.cs

/// <summary>
/// アイテムが追加されると呼び出されます。
/// </summary>
public event EventHandler AddItem;
/// <summary>
/// AddItemイベントを呼び出します。
/// </summary>
/// <param name="e"></param>
protected virtual void OnAddItem(EventArgs e) {
    if(AddItem != null) AddItem(this, e);
}

private void AddButton_Click(object sender, RoutedEventArgs e) {
    string uri = string.Format("http://localhost:1030/AddItem.aspx?t={0}&desc={1}",
        HttpUtility.UrlEncode(Title.Text),
        HttpUtility.UrlEncode(Description.Text)
    );
    WebClient webClient = new WebClient();
    webClient.DownloadStringCompleted += (_s, _e) => {
        OnAddItem(EventArgs.Empty);

        Close();
    };
    webClient.DownloadStringAsync(new Uri(uri));
}
private void CancelButton_Click(object sender, RoutedEventArgs e) {
    Close();
}

/// <summary>
/// 画面を閉じます。
/// </summary>
public void Close() {
    this.Visibility = Visibility.Collapsed;
}

アイテムが追加された事を呼び出し側(メイン画面)に伝える必要があるので、「AddItem」というイベントとそれを呼び出す為のヘルパーメソッドを定義している。

AddButton_Clickイベントハンドラでは「AddItem.aspx」に対してリクエスト(クエリ引数のエンコードもお忘れなく)を行い、DownloadStringCompletedイベントでレスポンスが返ってきてから、AddItemイベントを呼び出している(このイベントで呼び出し側はデータを更新すればいい)。

この処理を呼び出している間、UIをブロックする必要があるけど、それはまたあとでやる。

あとはメイン画面側にアイテムが追加された時の処理を実装する必要があるので、AddItemViewにイベントハンドラを追加する。

Page.xaml
<My:AddItemView x:Name="AddItemView" Grid.RowSpan="3" Visibility="Collapsed"
                AddItem="AddItemView_AddItem" />

イベントハンドラの処理。

Page.xaml.cs
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
    LoadAllItems();
}
private void AddItemView_AddItem(object sender, EventArgs e) {
    LoadAllItems();
}

/// <summary>
/// 全アイテムをロードします。
/// </summary>
private void LoadAllItems() {
    WebClient webClient = new WebClient();
    webClient.DownloadStringCompleted += (_s, _e) => {
        XDocument xml = XDocument.Parse(_e.Result);

        var items = from item in xml.Descendants("Item")
                    select new TodoItem {
                        Title = (string)item.Element("Title"),
                        Description = (string)item.Element("Description"),
                        Complete = (bool)item.Element("Complete")
                    };

        ItemList.ItemsSource = items;
    };
    webClient.DownloadStringAsync(new Uri("http://localhost:1030/ListItem.aspx"));
}

アイテムをロードする処理をLoadAllItemsというメソッドに切り離して、AddItemView_AddItemイベントハンドラでも呼び出すようにした。

これで実行すると、アイテムを追加できるようになった。でも何故かIE7ではアイテムの情報が更新されない。Firefoxでは正常に画面が更新されたけど。キャッシュ関係のような気がする。

アイテム追加画面の入力情報のクリア

アイテム追加画面に入力した情報が追加画面を閉じても残り続けるので、それをクリアするために以下のメソッドを定義する。

AddItemView.xaml.cs
/// <summary>
/// 画面を表示します。
/// </summary>
public void Show() {
    Title.Focus();
    Title.Text = Description.Text = string.Empty;

    this.Visibility = Visibility.Visible;
}

タイトルと詳細情報のテキストボックスをクリアして、タイトルのテキストボックスにフォーカスを合わせている。

そして、アイテム追加画面を表示する時にこのメソッドを使用するようにする。

Page.xaml.cs
private void AddButton_Click(object sender, RoutedEventArgs e) {
    AddItemView.Show();
}

これで毎回入力情報がリフレッシュされる。

アイテム追加画面の入力値チェック

タイトルを入力しなくてもAddボタンが押せてしまうので、タイトルを入力しないとAddボタンが押せないようにする。

まずタイトルのテキストボックスのTextChangedイベントにイベントハンドラを追加する。

AddItemView.xaml
<TextBox x:Name="Title" Grid.Column="1" Margin="4" TextChanged="Title_TextChanged" />

イベントハンドラの処理。

AddItemView.xaml.cs
private void Title_TextChanged(object sender, TextChangedEventArgs e) {
    AddButton.IsEnabled = (Title.Text.Trim().Length > 0);
}

タイトルが入力されていたら(空白は無視)、Addボタンを有効にする。

あと、Addボタンの初期状態を無効にしておく。

AddItemView.xaml
<Button x:Name="AddButton" Content="Add" IsEnabled="False"
        Width="60" Height="30" Margin="4" Click="AddButton_Click" />

Silverlight(というかWPF)での標準的なValidationはどうやってやるんだろう?