仕上げ
ListViewの実質的な定義は完了しましたが、この記事では話を簡単にするために一部の重要なコードの説明を割愛しています。その部分の詳細については、サンプルコードをご覧ください。省略した部分のコードでは、IValueConverterを持つもう1つのDataTemplateを使用して、startDateTemplateのComboBoxに表示する日付のフォーマットを変更しています。
既にListViewは定義されているので、XAMLのコードビハインドにイベントハンドラを追加することができます。これらのハンドラはExpenseLineItemを追加または削除する処理をExpenseSheetWindowModelに委譲します。そのためには、ユーザーが追加または削除しようとしているListViewItemのインデックス(ユーザーが[Add]ボタンまたは[Delete]ボタンをクリックした項目のインデックス)を調べて、そのインデックスの次の位置への行項目の挿入、またはそのインデックスの位置の行項目の削除を実行できるようにする必要があります。サンプルコードのaddButton_ClickおよびdeleteButton_Clickの2つのイベントハンドラでは、ビジュアルツリーを上にたどってListViewItemを見つけ出し、それをListViewのItemContentGeneratorメソッドに渡すことによってインデックスを取得しています。
private void addButton_Click( object sender, RoutedEventArgs e) { int index = GetListViewItemIndex( e.OriginalSource as DependencyObject); presentationModel.InsertRowIntoCurrentExpenseSheet( index + 1, new ExpenseLineItem()); } private void deleteButton_Click( object sender, RoutedEventArgs e) { ItemCollection items = lineItemListView.Items; // don't delete last item if (items.Count == 1) { return; } int index = GetListViewItemIndex( e.OriginalSource as DependencyObject); presentationModel.RemoveRowFromCurrentExpenseSheet(index); } private int GetListViewItemIndex(DependencyObject depObj) { while (!(depObj is ListViewItem)) { depObj = VisualTreeHelper.GetParent(depObj); } return lineItemListView.ItemContainerGenerator. IndexFromContainer(depObj); }
ハンドラから呼び出されるExpenseSheetWindowModelのメソッドは、ExpenseSheetに格納されているExpenseLineItemのObservableCollectionに対して要素を挿入または削除する単純な処理を行います。この処理の対象はObservableCollectionであるため、それが変更された時点でPropertyChangedイベントが発生し、UIが自動的に再描画されます。
public void RemoveRowFromCurrentExpenseSheet(int index) { CurrentExpenseSheet.LineItems.RemoveAt(index); } public void InsertRowIntoCurrentExpenseSheet( int index, ExpenseLineItem lineItem) { CurrentExpenseSheet.LineItems.Insert(index, lineItem); }
基本原則の拡張的適用
この記事では、複数の編集可能な行項目を持つ動的ユーザーインターフェイスを構築するうえでPresentation Modelがどのように有効であるのかを示しました。サンプルコードの内容をご覧になれば、図1の左上のナビゲーションボタン(緑色の3角形と正方形が表示されているボタン)など、この記事では取り上げなかったその他のUI要素も、ExpenseSheetWindowModelによって制御されていることがわかるでしょう。ここで説明した考え方は、行項目を表示するUIの開発だけにとどまらず、データセットに合わせて複数の編集可能コンポーネントをさまざまなレイアウトで表示することが必要なUIの開発に応用できます。
Presentation Modelパターンを使用すると、ユーザーインターフェイスの状態と振る舞いをカプセル化することができます。この例のメソッドの本体はXAMLのコードビハインドに無理なく記述できるほど小さいので、Presentation Modelの導入は話を無駄に複雑にするだけではないかと考える人もいるでしょう。確かに、この例のように小さなアプリケーションの場合はそう言えるもしれませんが、構築するUIの規模が大きくなってくると、ロジックはたちまち複雑化して厄介なものとなります。さらに、その他の付加的な機能――たとえば時間のかかる処理のためのバックグラウンドスレッドの起動や、マウスカーソルの状態の設定、進捗インジケータやメッセージボックスの表示など――も必要となるので、コードは混沌として手に負えない状態に劣化し始めるでしょう。
しかし、Presentation Modelを使用すれば、UIとビジネスオブジェクトを抽象化されたクリーンな形で結び付けることができるので、将来、まったく異なる別のUIの構築が必要となった場合でも、中核的なロジックを実装し直す必要なしにUIの変更を実現できます。
