かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

更新系の処理を作ってみよう クライアント編

前回の記事で、サーバー側の実装は終わりました。ということで、今回は、クライアント側であるSilverlightのほうを作ってみようと思います。
因みに、この例ではSilverlight ToolkitのDataFormコントロールを使っているので、流れに沿って実装してみる人は以下のサイトから入手してください。

では、画面側作っていきます。
MainPage.xamlの検索ボタンの横に、編集・追加・削除ボタンを起きます。

<!-- 検索ボタンを置く -->
<Grid Grid.Row="2" Margin="5">
    <StackPanel Orientation="Horizontal">
        <Button Content="検索" Click="SearchButton_Click" 
            Style="{StaticResource commandButtonStyle}"/>
        <Button Content="編集" Click="EditButton_Click" 
            Style="{StaticResource commandButtonStyle}"/>
        <Button Content="追加" Click="AddButton_Click" 
            Style="{StaticResource commandButtonStyle}"/>
        <Button Content="削除" Click="DeleteButton_Click" 
            Style="{StaticResource commandButtonStyle}"/>
    </StackPanel>
</Grid>

それぞれ、コードビハインド側にクリックイベントハンドラを空で作っておきます。

削除処理から作っていこう

編集や、追加は専用のUIを作らないといけない(DataGridでやるのもありだけど今回は別に画面を作ります)ので、一番簡単に作れる削除から作っていきます。作りかたは簡単で、削除ボタンが押された段階で、選択されている行を消します。

// 削除ボタン
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
    // 現在選択してる数だけループ
    // ここでList化しておかないと、foreachで回ってる間にコレクションに変更があったと
    // 例外が出ます。
    foreach (var emp in dataGrid.SelectedItems.Cast<Employee>().ToList())
    {
        // ローカルのデータ削除
        _context.Employees.Remove(emp);
    }
    // サーバーに削除要求送る前に画面を無効化
    this.IsEnabled = false;
    // サーバーに変更内容を送信
    _context.SubmitChanges(SubmitChangedCallback, null);
}


// SubmitChangesのコールバック
private void SubmitChangedCallback(SubmitOperation op)
{
    // 画面を有効化
    this.IsEnabled = true;
    if (op.HasError)
    {
        // エラーがあれば、とりあえずそのまま例外のメッセージを表示する
        MessageBox.Show(op.Error.Message);
    }
}

更新系の処理は、DomainContextのEntityCollectionのプロパティ(この場合はEmployeesプロパティ)に対して変更を加えて、SubmitChangesメソッドを呼ぶことで、ローカルでの変更履歴がサーバに送られて、サーバーサイドでは、対応したメソッドが順次呼び出されます。
なので、削除処理では、DataGridのSelectedItemsから、選択中の要素を取得し、Employeesプロパティから削除しています。
そして、最後にSubmitChangesを呼んでいます。SubmitChangesに渡すコールバックは、基本的に編集や追加でも同じなので、メソッドとして切り出しておきました。

編集・追加用画面の作成

次に編集と追加でしようする編集用画面を作成します。これは今回はChildWindowで実装しました。ChildWindowをEmployeeEditWindowという名前で作成して、DataFormを置いて編集時のテンプレートにDataFieldをEmployeeクラスのプロパティ数ぶん定義しています。

<controls:ChildWindow xmlns:dataFormToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"  x:Class="CrudRIAServices.EmployeeEditWindow"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
           Title="従業員データ編集"
           Width="500" Height="450">
    <Grid x:Name="LayoutRoot" Margin="2">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid>
            <!-- DataFormで従業員編集フォームを作る -->
            <!-- CommandButtonsVisibilityで、デフォルトのボタンを消している -->
            <dataFormToolkit:DataForm x:Name="dataForm"
                AutoGenerateFields="False"
                CurrentItem="{Binding}" 
                AutoEdit="True"
                CommandButtonsVisibility="None">
                <dataFormToolkit:DataForm.EditTemplate>
                    <!-- 編集時のテンプレート -->
                    <DataTemplate>
                        <StackPanel>
                            <!-- StackPanelで、適当に編集項目を並べる -->
                            <dataFormToolkit:DataField Label="ID">
                                <TextBox Text="{Binding ID, Mode=TwoWay}" />
                            </dataFormToolkit:DataField>
                            <dataFormToolkit:DataField Label="従業員名">
                                <TextBox Text="{Binding Name, Mode=TwoWay}" />
                            </dataFormToolkit:DataField>
                            <dataFormToolkit:DataField Label="入社年月日">
                                <controls:DatePicker SelectedDate="{Binding EntDate, Mode=TwoWay}" />
                            </dataFormToolkit:DataField>
                            <dataFormToolkit:DataField Label="給料">
                                <TextBox Text="{Binding Salary, Mode=TwoWay}" />
                            </dataFormToolkit:DataField>
                        </StackPanel>
                    </DataTemplate>
                </dataFormToolkit:DataForm.EditTemplate>
            </dataFormToolkit:DataForm>
        </Grid>

        <Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
        <Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
    </Grid>
</controls:ChildWindow>
using System.Windows;
using System.Windows.Controls;
using CrudRIAServices.Web;
using System.Windows.Ria.Data;

namespace CrudRIAServices
{
    public partial class EmployeeEditWindow : ChildWindow
    {
        public EmployeeEditWindow()
        {
            InitializeComponent();
        }

        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            var emp = (Employee)DataContext;
            // 何も変更されていなければ、何か編集しろと表示する
            if (emp.EntityState != EntityState.New && !emp.HasChanges)
            {
                MessageBox.Show("データを入力して下さい");
                return;
            }

            // コミットが成功したら、DialogResultをTrueに設定して画面を閉じる
            if (dataForm.CommitEdit())
            {
                this.DialogResult = true;
            }

            // コミットが失敗したら、画面閉じない(Cancelを押してもらう)
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            // 編集をキャンセルして、DialogResultにfalseを代入する。
            dataForm.CancelEdit();
            this.DialogResult = false;
        }
    }
}

OKを、押した時は、データが更新されていたら、編集をコミットしてDialogReusltをtrueに設定しています。Cancelが押された時は、編集をキャンセルしてDialogResultにfalseを設定しています。

次に、MainPage側のロジックを作りこんでいきます。編集も追加も、EmployeeEditWindowに編集対象の従業員をセットして、画面を表示して、OKボタンが押されたら追加の時だけEmployeesプロパティにデータを追加してSubmitChangesを呼ぶという流れなのでメソッドにこの流れを切り出しました。
追加のときに、Employeesにデータを追加する処理を挟み込めるように、デリゲートでOKボタン押された後に任意の処理を指定できるようにしています。

#region 追加・更新時の共通ロジック
/// <summary>
/// 引数で渡した従業員を編集する
/// </summary>
/// <param name="editTarget">編集対象の従業員</param>
private void Edit(Employee editTarget)
{
    Edit(editTarget, w => { });
}

/// <summary>
/// 引数で渡した従業員を編集する。
/// closedActionに、編集画面がOKボタンを押して閉じられた時に行う
/// 処理を記述できる。
/// </summary>
/// <param name="editTarget">編集対象の従業員</param>
/// <param name="closedAction">
/// 編集画面でOKボタンが押された時に呼ばれるコールバック。
/// 引数に編集後の従業員データが渡される。
/// </param>
private void Edit(Employee editTarget, Action<Employee> closedAction)
{
    // 従業員編集画面を作成(DataContextに編集対象の従業員を入れておく)
    var window = new EmployeeEditWindow { DataContext = editTarget };

    window.Closed += (sender, e) =>
    {
        if (window.DialogResult == true)
        {
            // OKが押されたら
            // 更新要求のため画面を無効化
            this.IsEnabled = false;
            // コールバックを呼び出し
            closedAction((Employee)window.DataContext);
            // 変更内容をサーバーに送信
            _context.SubmitChanges(SubmitChangedCallback, null);
        }
    };
    // 画面を表示する
    window.Show();
}
#endregion

後は、これを使うように、編集ボタンと追加ボタンのクリックイベントハンドラをさくっと作ります。

// 編集ボタン
private void EditButton_Click(object sender, RoutedEventArgs e)
{
    // 選択中の従業員がいなかったら何もしない
    if (dataGrid.SelectedItem == null)
    {
        return;
    }

    // 選択中の従業員を編集する
    Edit((Employee) dataGrid.SelectedItem);
}

// 追加ボタン
private void AddButton_Click(object sender, RoutedEventArgs e)
{
    // 新しい従業員を追加する
    Edit(new Employee { EntDate = DateTime.UtcNow },
        // OKが押されたら、そのデータをDomainContextに追加
        employee =>
        {
            _context.Employees.Add(employee);
        });
}

以上で完成です!動かしてみます。

お給料が20万の悲しい人を検索したところ

削除候補の人を選択して

削除ボタンをぽちっとな

消えてます。

追加ボタンを押したところ

適当なデータを入れてます。

OKを押すと追加されてます

編集対象の人を選んで

編集ボタンを押して適当に編集したところ

OKを押すと編集結果が反映されています

一応これで、簡単な検索と入力チェックを何もしない感じですが、更新系も一通りできるようになりました。この状態のプロジェクトは↓からダウンロードできます。
http://cid-c0989b857f2f850c.skydrive.live.com/self.aspx/Silverlight/RIAServices/CrudRIAServices.zip